simba-uw-tf-dev 4.7.1__py3-none-any.whl → 4.7.5__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.

Files changed (41) hide show
  1. simba/SimBA.py +13 -4
  2. simba/assets/icons/left_arrow_green.png +0 -0
  3. simba/assets/icons/left_arrow_red.png +0 -0
  4. simba/assets/icons/right_arrow_green.png +0 -0
  5. simba/assets/icons/right_arrow_red.png +0 -0
  6. simba/assets/lookups/yolo_schematics/yolo_mitra.csv +9 -0
  7. simba/mixins/geometry_mixin.py +357 -302
  8. simba/mixins/image_mixin.py +129 -4
  9. simba/mixins/train_model_mixin.py +1 -4
  10. simba/model/inference_batch.py +1 -1
  11. simba/model/yolo_fit.py +22 -15
  12. simba/model/yolo_pose_inference.py +7 -2
  13. simba/outlier_tools/skip_outlier_correction.py +2 -2
  14. simba/plotting/heat_mapper_clf_mp.py +45 -23
  15. simba/plotting/plot_clf_results.py +2 -1
  16. simba/plotting/plot_clf_results_mp.py +456 -455
  17. simba/roi_tools/roi_utils.py +2 -2
  18. simba/sandbox/convert_h264_to_mp4_lossless.py +129 -0
  19. simba/sandbox/extract_and_convert_videos.py +257 -0
  20. simba/sandbox/remove_end_of_video.py +80 -0
  21. simba/sandbox/video_timelaps.py +291 -0
  22. simba/third_party_label_appenders/transform/simba_to_yolo.py +8 -5
  23. simba/ui/import_pose_frame.py +13 -13
  24. simba/ui/pop_ups/clf_plot_pop_up.py +1 -1
  25. simba/ui/pop_ups/run_machine_models_popup.py +22 -22
  26. simba/ui/pop_ups/simba_to_yolo_keypoints_popup.py +2 -2
  27. simba/ui/pop_ups/video_processing_pop_up.py +3638 -3469
  28. simba/ui/pop_ups/yolo_inference_popup.py +1 -1
  29. simba/ui/pop_ups/yolo_pose_train_popup.py +1 -1
  30. simba/ui/tkinter_functions.py +3 -1
  31. simba/ui/video_timelaps.py +454 -0
  32. simba/utils/lookups.py +67 -1
  33. simba/utils/read_write.py +10 -3
  34. simba/video_processors/batch_process_create_ffmpeg_commands.py +0 -1
  35. simba/video_processors/video_processing.py +160 -39
  36. {simba_uw_tf_dev-4.7.1.dist-info → simba_uw_tf_dev-4.7.5.dist-info}/METADATA +1 -1
  37. {simba_uw_tf_dev-4.7.1.dist-info → simba_uw_tf_dev-4.7.5.dist-info}/RECORD +41 -31
  38. {simba_uw_tf_dev-4.7.1.dist-info → simba_uw_tf_dev-4.7.5.dist-info}/LICENSE +0 -0
  39. {simba_uw_tf_dev-4.7.1.dist-info → simba_uw_tf_dev-4.7.5.dist-info}/WHEEL +0 -0
  40. {simba_uw_tf_dev-4.7.1.dist-info → simba_uw_tf_dev-4.7.5.dist-info}/entry_points.txt +0 -0
  41. {simba_uw_tf_dev-4.7.1.dist-info → simba_uw_tf_dev-4.7.5.dist-info}/top_level.txt +0 -0
@@ -52,12 +52,12 @@ from simba.utils.errors import (CountError, DirectoryExistError,
52
52
  NoDataError, NoFilesFoundError,
53
53
  NotDirectoryError, ResolutionError,
54
54
  SimBAGPUError)
55
- from simba.utils.lookups import (get_current_time,
55
+ 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
59
  video_quality_to_preset_lookup)
60
- from simba.utils.printing import SimbaTimer, stdout_success
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,
63
63
  concatenate_videos_in_folder, create_directory,
@@ -704,12 +704,9 @@ def change_single_video_fps(file_path: Union[str, os.PathLike],
704
704
  quality = 23 if not check_int(name='quality', value=quality, min_value=0, max_value=52, raise_error=False)[0] else int(quality)
705
705
  if verbose: print(f"Converting the FPS {video_meta_data['fps']} -> {fps} for video {file_name} ...")
706
706
  if codec is None:
707
- if ext.lower() == '.webm':
708
- codec = 'libvpx-vp9'
709
- elif ext.lower() == '.avi':
710
- codec = 'mpeg4'
711
- else:
712
- codec = 'libx264'
707
+ codec = get_ffmpeg_codec(file_name=file_path)
708
+ else:
709
+ check_valid_codec(codec=codec, raise_error=True, source=change_single_video_fps.__name__)
713
710
  if os.path.isfile(save_path):
714
711
  FileExistWarning(msg=f"Overwriting existing file at {save_path}...", source=change_single_video_fps.__name__,)
715
712
  if gpu:
@@ -742,6 +739,9 @@ def change_fps_of_multiple_videos(path: Union[str, os.PathLike, List[Union[str,
742
739
  :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
743
740
  :param bool verbose: If True, prints conversion progress. Default True.
744
741
  :returns: None.
742
+
743
+ .. note::
744
+ Codec is automatically selected based on file extension: libvpx-vp9 for .webm, mpeg4 for .avi, libx264 for others.
745
745
 
746
746
  :example:
747
747
  >>> _ = change_fps_of_multiple_videos(path='project_folder/videos/Video_1.mp4', fps=15)
@@ -800,13 +800,17 @@ def change_fps_of_multiple_videos(path: Union[str, os.PathLike, List[Union[str,
800
800
  if verbose: stdout_success(msg=f"SIMBA COMPLETE: FPS of {len(video_paths)} video(s) changed to {fps}", elapsed_time=timer.elapsed_time_str, source=change_fps_of_multiple_videos.__name__,)
801
801
 
802
802
 
803
- def convert_video_powerpoint_compatible_format(file_path: Union[str, os.PathLike], gpu: Optional[bool] = False) -> None:
803
+ def convert_video_powerpoint_compatible_format(file_path: Union[str, os.PathLike],
804
+ gpu: Optional[bool] = False) -> None:
804
805
  """
805
806
  Create MS PowerPoint compatible copy of a video file.
806
807
 
807
808
  :param Union[str, os.PathLike] file_path: Path to video file.
808
809
  :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
809
810
  :returns: None. The result is stored in the same directory as the input file with the ``_powerpointready`` suffix.
811
+
812
+ .. note::
813
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
810
814
 
811
815
  :example:
812
816
  >>> _ = convert_video_powerpoint_compatible_format(file_path='project_folder/videos/Video_1.mp4')
@@ -846,7 +850,7 @@ def convert_video_powerpoint_compatible_format(file_path: Union[str, os.PathLike
846
850
 
847
851
  def video_to_greyscale(file_path: Union[str, os.PathLike],
848
852
  gpu: Optional[bool] = False,
849
- codec: str = 'libx264',
853
+ codec: Optional[str] = None,
850
854
  verbose: bool = True,
851
855
  quality: int = 23,
852
856
  save_path: Optional[Union[str, os.PathLike]] = None) -> None:
@@ -890,6 +894,10 @@ def video_to_greyscale(file_path: Union[str, os.PathLike],
890
894
  check_if_dir_exists(in_dir=os.path.dirname(save_path))
891
895
  save_name = deepcopy(save_path)
892
896
  quality = 23 if not check_int(name='quality', value=quality, min_value=0, max_value=52, raise_error=False)[0] else int(quality)
897
+ if codec is None:
898
+ codec = get_ffmpeg_codec(file_name=file_path)
899
+ else:
900
+ check_valid_codec(codec=codec, raise_error=True, source=video_to_greyscale.__name__)
893
901
  if gpu:
894
902
  cmd = f'ffmpeg -hwaccel auto -c:v h264_cuvid -i "{file_path}" -vf "hwupload_cuda, hwdownload, format=nv12, format=gray" -c:v h264_nvenc -rc vbr -cq {quality} -c:a copy "{save_name}" -loglevel error -stats -hide_banner -y'
895
903
  else:
@@ -924,6 +932,9 @@ def batch_video_to_greyscale(path: Union[str, os.PathLike, List[Union[str, os.Pa
924
932
  :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
925
933
  :raise FFMPEGCodecGPUError: If no GPU is found and ``gpu == True``.
926
934
  :returns: None.
935
+
936
+ .. note::
937
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
927
938
 
928
939
  :example:
929
940
  >>> _ = batch_video_to_greyscale(path='/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/project_folder/videos/test_2')
@@ -971,7 +982,7 @@ def superimpose_frame_count(file_path: Union[str, os.PathLike],
971
982
  font: Optional[str] = 'Arial',
972
983
  font_color: Optional[str] = 'black',
973
984
  bg_color: Optional[str] = 'white',
974
- codec: Optional[str] = 'libx264',
985
+ codec: Optional[str] = None,
975
986
  quality: Optional[int] = None,
976
987
  verbose: bool = True,
977
988
  save_path: Optional[Union[str, os.PathLike]] = None,
@@ -1037,11 +1048,13 @@ def superimpose_frame_count(file_path: Union[str, os.PathLike],
1037
1048
  print(len(file_paths), file_path)
1038
1049
  else:
1039
1050
  raise InvalidInputError(msg=f'{file_path} is not a valid file path or file directory.', source=superimpose_frame_count.__name__)
1040
-
1051
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=superimpose_frame_count.__name__)
1041
1052
  quality = 23 if not check_int(name='quality', value=quality, min_value=0, max_value=52, raise_error=False)[0] else int(quality)
1042
1053
  for video_cnt, file_path in enumerate(file_paths):
1043
1054
  dir, file_name, ext = get_fn_ext(filepath=file_path)
1044
- if verbose: print(f'Superimposing frame count video {video_cnt+1}/{len(file_paths)}...')
1055
+ if verbose:
1056
+ stdout_information(msg=f'Superimposing frame count video {video_cnt+1}/{len(file_paths)}...', source=change_single_video_fps.__name__)
1057
+ codec = get_ffmpeg_codec(file_name=file_path) if codec is None else codec
1045
1058
  if save_path is None:
1046
1059
  save_name = os.path.join(dir, f"{file_name}_frame_no.mp4")
1047
1060
  elif os.path.isdir(save_path):
@@ -1088,12 +1101,18 @@ def remove_beginning_of_video(file_path: Union[str, os.PathLike],
1088
1101
  """
1089
1102
  Remove N seconds from the beginning of a video file.
1090
1103
 
1104
+ .. seealso::
1105
+ To remove N seconds from the end of the video, see :func:`simba.video_processors.video_processing.remove_end_of_video`.
1106
+
1091
1107
  :param Union[str, os.PathLike] file_path: Path to video file
1092
1108
  :param int time: Number of seconds to remove from the beginning of the video.
1093
1109
  :param int quality: Video quality percentage (1-100). Higher values = higher quality. Default 60.
1094
1110
  :param Optional[Union[str, os.PathLike]] save_path: Optional save location for the shortened video. If None, then the new video is saved in the same directory as the input video with the ``_shortened`` suffix.
1095
1111
  :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
1096
1112
  :returns: None. If save_path is not passed, the result is stored in the same directory as the input file with the ``_shorten.mp4`` suffix.
1113
+
1114
+ .. note::
1115
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
1097
1116
 
1098
1117
  :example:
1099
1118
  >>> _ = remove_beginning_of_video(file_path='project_folder/videos/Video_1.avi', time=10)
@@ -1128,12 +1147,72 @@ def remove_beginning_of_video(file_path: Union[str, os.PathLike],
1128
1147
  stdout_success(msg=f"SIMBA COMPLETE: Video converted! {save_name} generated!", elapsed_time=timer.elapsed_time_str, source=remove_beginning_of_video.__name__)
1129
1148
 
1130
1149
 
1150
+ def remove_end_of_video(file_path: Union[str, os.PathLike],
1151
+ time: int,
1152
+ quality: int = 60,
1153
+ save_path: Optional[Union[str, os.PathLike]] = None,
1154
+ gpu: Optional[bool] = False) -> None:
1155
+ """
1156
+ Remove N seconds from the end of a video file.
1157
+
1158
+ .. seealso::
1159
+ To remove N seconds from the beginning of the video, see :func:`simba.video_processors.video_processing.remove_beginning_of_video`
1160
+
1161
+ :param Union[str, os.PathLike] file_path: Path to video file
1162
+ :param int time: Number of seconds to remove from the end of the video.
1163
+ :param int quality: Video quality percentage (1-100). Higher values = higher quality. Default 60.
1164
+ :param Optional[Union[str, os.PathLike]] save_path: Optional save location for the shortened video. If None, then the new video is saved in the same directory as the input video with the ``_shortened`` suffix.
1165
+ :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
1166
+ :returns: None. If save_path is not passed, the result is stored in the same directory as the input file with the ``_shorten.mp4`` suffix.
1167
+
1168
+ .. note::
1169
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
1170
+
1171
+ :example:
1172
+ >>> _ = remove_end_of_video(file_path='project_folder/videos/Video_1.avi', time=10)
1173
+ >>> remove_end_of_video(file_path=f'/Users/simon/Desktop/imgs_4/test/blahhhh.mp4', save_path='/Users/simon/Desktop/imgs_4/test/CUT.mp4', time=3)
1174
+ """
1175
+
1176
+ check_ffmpeg_available(raise_error=True)
1177
+ if gpu and not check_nvidea_gpu_available():
1178
+ raise FFMPEGCodecGPUError(msg="No GPU found (as evaluated by nvidea-smi returning None)",
1179
+ source=remove_end_of_video.__name__)
1180
+ timer = SimbaTimer(start=True)
1181
+ check_file_exist_and_readable(file_path=file_path)
1182
+ video_meta_data = get_video_meta_data(video_path=file_path)
1183
+ check_int(name="Cut time", value=time, min_value=1)
1184
+ check_int(name=f'{remove_end_of_video.__name__} quality', value=quality, min_value=1, max_value=100,
1185
+ raise_error=True)
1186
+ quality_crf = quality_pct_to_crf(pct=int(quality))
1187
+ time = int(time)
1188
+ dir, file_name, ext = get_fn_ext(filepath=file_path)
1189
+ if video_meta_data['video_length_s'] <= time:
1190
+ raise InvalidInputError(
1191
+ msg=f"The cut time {time}s is invalid for video {file_name} with length {video_meta_data['video_length_s']}s",
1192
+ source=remove_end_of_video.__name__)
1193
+ if save_path is None:
1194
+ save_name = os.path.join(dir, f"{file_name}_shorten.mp4")
1195
+ else:
1196
+ check_if_dir_exists(in_dir=os.path.dirname(save_path), source=f'{remove_end_of_video.__name__} save_path',
1197
+ create_if_not_exist=True)
1198
+ save_name = save_path
1199
+ duration = video_meta_data['video_length_s'] - time
1200
+ if gpu:
1201
+ cmd = f'ffmpeg -hwaccel auto -c:v h264_cuvid -i "{file_path}" -t {duration} -rc vbr -cq {quality_crf} -c:v h264_nvenc -c:a aac "{save_name}" -loglevel error -stats -hide_banner -y'
1202
+ else:
1203
+ cmd = f'ffmpeg -i "{file_path}" -t {duration} -c:v libx264 -crf {quality_crf} -c:a aac "{save_name}" -loglevel error -stats -hide_banner -y'
1204
+ print(f"Removing final {time}s from {file_name}... ")
1205
+ subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
1206
+ timer.stop_timer()
1207
+ stdout_success(msg=f"SIMBA COMPLETE: Video converted! {save_name} generated!", elapsed_time=timer.elapsed_time_str, source=remove_end_of_video.__name__)
1208
+
1209
+
1131
1210
  def clip_video_in_range(file_path: Union[str, os.PathLike],
1132
1211
  start_time: str,
1133
1212
  end_time: str,
1134
1213
  out_dir: Optional[Union[str, os.PathLike]] = None,
1135
1214
  save_path: Optional[Union[str, os.PathLike]] = None,
1136
- codec: str = 'libvpx-vp9',
1215
+ codec: Optional[str] = None,
1137
1216
  quality: int = 60,
1138
1217
  verbose: bool = True,
1139
1218
  overwrite: Optional[bool] = False,
@@ -1147,7 +1226,7 @@ def clip_video_in_range(file_path: Union[str, os.PathLike],
1147
1226
  :param str end_time: End time in HH:MM:SS format.
1148
1227
  :param Optional[Union[str, os.PathLike]] out_dir: If None, then the clip will be stored in the same dir as the input video. If directory, then the location of the output files.
1149
1228
  :param Optional[Union[str, os.PathLike]] save_path: Optional save path for the clipped video. If provided, overrides out_dir and filename generation. Default None.
1150
- :param str codec: Video codec to use for CPU encoding. Default 'libvpx-vp9'. Ignored if gpu=True.
1229
+ :param Optional[str] codec: Video codec to use for CPU encoding. If None, automatically selects based on file extension (libvpx-vp9 for .webm, mpeg4 for .avi, libx264 for others). Default None. Ignored if gpu=True.
1151
1230
  :param int quality: Video quality percentage (0-100). Higher values = higher quality. Default 60.
1152
1231
  :param bool verbose: If True, prints conversion progress. Default True.
1153
1232
  :param Optional[bool] overwrite: If True, overwrite output file if path already exists. If False, then raise FileExistError. Default False.
@@ -1176,7 +1255,10 @@ def clip_video_in_range(file_path: Union[str, os.PathLike],
1176
1255
  check_if_hhmmss_timestamp_is_valid_part_of_video(timestamp=end_time, video_path=file_path)
1177
1256
  quality = 60 if not check_int(name='quality', value=quality, min_value=0, max_value=100, raise_error=False)[0] else int(quality)
1178
1257
  quality_crf = quality_pct_to_crf(pct=quality)
1179
- codec = 'libvpx-vp9' if ext.lower() == '.webm' else codec
1258
+ if codec is None:
1259
+ codec = get_ffmpeg_codec(file_name=file_path)
1260
+ else:
1261
+ check_valid_codec(codec=codec, raise_error=True, source=clip_video_in_range.__name__)
1180
1262
  if not include_clip_time_in_filename:
1181
1263
  save_name = os.path.join(dir, file_name + "_clipped.mp4")
1182
1264
  else:
@@ -1200,7 +1282,7 @@ def downsample_video(file_path: Union[str, os.PathLike],
1200
1282
  video_height: int,
1201
1283
  video_width: int,
1202
1284
  gpu: bool = False,
1203
- codec: str = 'libx264',
1285
+ codec: Optional[str] = None,
1204
1286
  quality: int = 23,
1205
1287
  save_path: Optional[Union[str, os.PathLike]] = None,
1206
1288
  verbose: bool = True) -> None:
@@ -1240,6 +1322,10 @@ def downsample_video(file_path: Union[str, os.PathLike],
1240
1322
  if os.path.isfile(save_name):
1241
1323
  raise FileExistError("SIMBA ERROR: The outfile file already exist: {}.".format(save_name), source=downsample_video.__name__)
1242
1324
  quality = 23 if not check_int(name=f'{downsample_video.__name__} quality', value=quality, min_value=0, max_value=52, raise_error=False)[0] else int(quality)
1325
+ if codec is None:
1326
+ codec = get_ffmpeg_codec(file_name=file_path)
1327
+ else:
1328
+ check_valid_codec(codec=codec, raise_error=True, source=downsample_video.__name__)
1243
1329
  if gpu:
1244
1330
  command = f'ffmpeg -y -hwaccel auto -c:v h264_cuvid -i "{file_path}" -vf "scale=w={video_width}:h={video_height}" -c:v h264_nvenc -rc vbr -cq {quality} "{save_name}" -hide_banner -loglevel error -stats -y'
1245
1331
  #command = f'ffmpeg -y -hwaccel auto -c:v h264_cuvid -i "{file_path}" -vf "scale_cuda=w={video_width}:h={video_height}:force_original_aspect_ratio=decrease:flags=bicubic" -c:v h264_nvenc -rc vbr -cq {quality} "{save_name}" -loglevel error -stats -hide_banner -y'
@@ -1332,6 +1418,9 @@ def batch_convert_video_format(directory: Union[str, os.PathLike],
1332
1418
  :parameter str output_format: Format of the output files (e.g., mp4).
1333
1419
  :parameter Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
1334
1420
  :returns: None. The results are stored in the same directory as the input files.
1421
+
1422
+ .. note::
1423
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
1335
1424
 
1336
1425
  :example:
1337
1426
  >>> _ = batch_convert_video_format(directory='project_folder/videos', input_format='avi', output_format='mp4')
@@ -1487,6 +1576,7 @@ def multi_split_video(file_path: Union[str, os.PathLike],
1487
1576
  end_times: List[str],
1488
1577
  out_dir: Optional[Union[str, os.PathLike]] = None,
1489
1578
  quality: Optional[int] = None,
1579
+ codec: Optional[str] = None,
1490
1580
  include_clip_time_in_filename: Optional[bool] = False,
1491
1581
  gpu: Optional[bool] = False) -> None:
1492
1582
  """
@@ -1499,6 +1589,7 @@ def multi_split_video(file_path: Union[str, os.PathLike],
1499
1589
  :param Optional[int] quality: Video quality (CRF value). Lower values = higher quality. Range 0-52. If None, defaults to 23. Default None.
1500
1590
  :param Optional[bool] include_clip_time_in_filename: If True, include the clip start and end in HH-MM-SS format as suffix in the filename. If False, then use integer suffic representing the count.
1501
1591
  :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
1592
+ :param Optional[str] codec: Video codec to use for CPU encoding. If None, automatically selects based on file extension (libvpx-vp9 for .webm, mpeg4 for .avi, libx264 for others). Default None. Ignored if gpu=True.
1502
1593
  :returns: None.
1503
1594
 
1504
1595
  :example:
@@ -1510,6 +1601,8 @@ def multi_split_video(file_path: Union[str, os.PathLike],
1510
1601
  check_file_exist_and_readable(file_path=file_path)
1511
1602
  dir_name, file_name, ext = get_fn_ext(filepath=file_path)
1512
1603
  quality = 23 if not check_int(name=f'{multi_split_video.__name__} quality', value=quality, min_value=0, max_value=52, raise_error=False)[0] else int(quality)
1604
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=multi_split_video.__name__)
1605
+ codec = get_ffmpeg_codec(file_name=file_name) if codec is None else codec
1513
1606
  if out_dir is not None:
1514
1607
  if not os.path.isdir(out_dir):
1515
1608
  os.makedirs(out_dir)
@@ -1555,7 +1648,7 @@ def multi_split_video(file_path: Union[str, os.PathLike],
1555
1648
  )
1556
1649
  command = f'ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i "{file_path}" -ss {start_time} -to {end_time} -c:v h264_nvenc -rc vbr -cq {quality} "{save_path}" -hide_banner -loglevel error -stats -y'
1557
1650
  else:
1558
- command = f'ffmpeg -i "{file_path}" -ss {start_time} -to {end_time} -async 1 -crf {quality} "{save_path}" -loglevel error -stats -hide_banner -y'
1651
+ command = f'ffmpeg -i "{file_path}" -ss {start_time} -to {end_time} -c:v {codec} -async 1 -crf {quality} "{save_path}" -loglevel error -stats -hide_banner -y'
1559
1652
  clip_timer = SimbaTimer(start=True)
1560
1653
  subprocess.call(command, shell=True, stdout=subprocess.PIPE)
1561
1654
  clip_timer.stop_timer()
@@ -1623,6 +1716,7 @@ def crop_single_video(file_path: Union[str, os.PathLike],
1623
1716
  def crop_multiple_videos(directory_path: Union[str, os.PathLike],
1624
1717
  output_path: Union[str, os.PathLike],
1625
1718
  gpu: Optional[bool] = False,
1719
+ codec: Optional[str] = None,
1626
1720
  quality: int = 60) -> None:
1627
1721
  """
1628
1722
  Crop multiple videos in a folder according to crop-coordinates defined in the **first** video.
@@ -1636,6 +1730,8 @@ def crop_multiple_videos(directory_path: Union[str, os.PathLike],
1636
1730
  :param Union[str, os.PathLike] directory_path: Directory containing input videos.
1637
1731
  :param Union[str, os.PathLike] output_path: Directory where to save the cropped videos.
1638
1732
  :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
1733
+ :param Optional[str] codec: Video codec to use for CPU encoding. If None, automatically selects based on file extension (libvpx-vp9 for .webm, mpeg4 for .avi, libx264 for others). Default None. Ignored if gpu=True.
1734
+ :param int quality: Video quality percentage (1-100). Higher values = higher quality. Default 60.
1639
1735
  :returns: None. Results are stored in passed ``output_path``.
1640
1736
 
1641
1737
  :example:
@@ -1661,15 +1757,18 @@ def crop_multiple_videos(directory_path: Union[str, os.PathLike],
1661
1757
  if ((roi_selector.top_left[0] < 0) or (roi_selector.top_left[1] < 0) or (roi_selector.bottom_right[0] < 0) or (roi_selector.bottom_right[1] < 1)):
1662
1758
  raise CountError(msg=f"CROP FAILED: Cannot use negative crop coordinates. Got top_left: {roi_selector.top_left}, bottom_right: {roi_selector.bottom_right}", source=crop_multiple_videos.__name__)
1663
1759
  timer = SimbaTimer(start=True)
1760
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=change_single_video_fps.__name__)
1761
+
1664
1762
  for file_cnt, file_path in enumerate(video_paths):
1665
1763
  video_timer = SimbaTimer(start=True)
1666
1764
  dir_name, file_name, ext = get_fn_ext(filepath=file_path)
1765
+ video_codec = Formats.BATCH_CODEC.value if codec is None else get_ffmpeg_codec(file_name=file_path)
1667
1766
  print(f"Cropping video {file_name} ({file_cnt+1}/{len(video_paths)})...")
1668
1767
  video_meta_data = get_video_meta_data(file_path)
1669
1768
  if (roi_selector.bottom_right[0] > video_meta_data["width"]) or (roi_selector.bottom_right[1] > video_meta_data["height"]):
1670
1769
  raise InvalidInputError(msg=f'Cannot crop video {file_name} of size {video_meta_data["resolution_str"]} at location top left: {roi_selector.top_left}, bottom right: {roi_selector.bottom_right}', source=crop_multiple_videos.__name__)
1671
1770
  save_path = os.path.join(output_path, f"{file_name}_cropped.mp4")
1672
- crop_video(video_path=file_path, save_path=save_path, size=(roi_selector.width, roi_selector.height), top_left=(roi_selector.top_left[0], roi_selector.top_left[1]), gpu=gpu, verbose=False, quality=quality)
1771
+ crop_video(video_path=file_path, save_path=save_path, size=(roi_selector.width, roi_selector.height), top_left=(roi_selector.top_left[0], roi_selector.top_left[1]), gpu=gpu, verbose=False, quality=quality, codec=video_codec)
1673
1772
  video_timer.stop_timer()
1674
1773
  print(f"Video {file_name} cropped (Video {file_cnt+1}/{len(video_paths)}, elapsed time: {video_timer.elapsed_time_str})")
1675
1774
  timer.stop_timer()
@@ -1692,6 +1791,9 @@ def frames_to_movie(directory: Union[str, os.PathLike],
1692
1791
  :param out_format: Format of the output video. One of: 'mp4', 'avi', 'webm'. Default is 'mp4'.
1693
1792
  :param gpu: If True, use NVIDIA GPU codecs (if available). Default is False.
1694
1793
  :return: None. Video is saved to disk.
1794
+
1795
+ .. note::
1796
+ Codec is automatically selected based on out_format: libvpx-vp9 for 'webm', mpeg4 for 'avi', libx264 for 'mp4' (ignored if gpu=True).
1695
1797
  """
1696
1798
 
1697
1799
  import re
@@ -1776,6 +1878,9 @@ def video_concatenator(video_one_path: Union[str, os.PathLike],
1776
1878
  :param Optional[int] quality: Integer (1-100) representing output video quality. Higher = better quality + bigger size. If None, defaults to 60. Default None.
1777
1879
  :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
1778
1880
  :returns: None. The video is stored in the same directory as the ``video_one_path`` using the video names concatenated as filename.
1881
+
1882
+ .. note::
1883
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
1779
1884
 
1780
1885
  :example:
1781
1886
  >>> video_concatenator(video_one_path='project_folder/videos/Video_1.mp4', video_two_path='project_folder/videos/Video_2.mp4', resolution=800, horizontal=True)
@@ -2400,6 +2505,7 @@ def resize_videos_by_height(video_paths: List[Union[str, os.PathLike]],
2400
2505
  gpu: Optional[bool] = False,
2401
2506
  quality: Optional[int] = None,
2402
2507
  suffix: Optional[str] = None,
2508
+ codec: Optional[str] = None,
2403
2509
  verbose: Optional[bool] = True) -> Union[None, List[Union[None, str, os.PathLike]]]:
2404
2510
  """
2405
2511
  Re-size a list of videos to a specified height while retaining their aspect ratios.
@@ -2442,10 +2548,13 @@ def resize_videos_by_height(video_paths: List[Union[str, os.PathLike]],
2442
2548
  for i in video_paths:
2443
2549
  video_heights.append(get_video_meta_data(video_path=i)["height"])
2444
2550
  height = video_heights[int(height)]
2551
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=resize_videos_by_height.__name__)
2552
+
2445
2553
  for cnt, video_path in enumerate(video_paths):
2446
2554
  dir_name, video_name, ext = get_fn_ext(video_path)
2555
+ video_codec = get_ffmpeg_codec(file_name=video_path) if codec is None else codec
2447
2556
  if verbose:
2448
- print(f"Resizing height video {video_name} (Video {cnt+1}/{len(video_paths)})...")
2557
+ stdout_information(msg=f"Resizing height video {video_name} (Video {cnt+1}/{len(video_paths)})...", source=resize_videos_by_height.__name__)
2449
2558
  if overwrite:
2450
2559
  dt = datetime.now().strftime("%Y%m%d%H%M%S")
2451
2560
  save_path = os.path.join(dir_name, f"{video_name}_{dt}.mp4")
@@ -2459,7 +2568,7 @@ def resize_videos_by_height(video_paths: List[Union[str, os.PathLike]],
2459
2568
  if gpu:
2460
2569
  cmd = f'ffmpeg -y -hwaccel auto -c:v h264_cuvid -i "{video_path}" -vf scale_npp=-2:{height} -c:v h264_nvenc -rc vbr -cq {quality} "{save_path}" -hide_banner -loglevel error -y'
2461
2570
  else:
2462
- cmd = f'ffmpeg -y -i "{video_path}" -vf scale=-2:{height} -crf {quality} "{save_path}" -hide_banner -loglevel error -stats -y'
2571
+ cmd = f'ffmpeg -y -i "{video_path}" -vf scale=-2:{height} -c:v {video_codec} -crf {quality} "{save_path}" -hide_banner -loglevel error -stats -y'
2463
2572
  subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
2464
2573
  if overwrite:
2465
2574
  shutil.copy(save_path, video_path)
@@ -2477,6 +2586,7 @@ def resize_videos_by_width(video_paths: List[Union[str, os.PathLike]],
2477
2586
  overwrite: Optional[bool] = False,
2478
2587
  save_dir: Optional[Union[str, os.PathLike]] = None,
2479
2588
  gpu: Optional[bool] = False,
2589
+ codec: Optional[str] = None,
2480
2590
  quality: Optional[int] = None,
2481
2591
  suffix: Optional[str] = None,
2482
2592
  verbose: Optional[bool] = True) -> Union[None, List[Union[None, str, os.PathLike]]]:
@@ -2527,12 +2637,12 @@ def resize_videos_by_width(video_paths: List[Union[str, os.PathLike]],
2527
2637
  for i in video_paths:
2528
2638
  video_widths.append(get_video_meta_data(video_path=i)["width"])
2529
2639
  width = video_widths[int(width)]
2640
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=resize_videos_by_width.__name__)
2530
2641
  for cnt, video_path in enumerate(video_paths):
2531
2642
  dir_name, video_name, ext = get_fn_ext(video_path)
2643
+ video_codec = get_ffmpeg_codec(file_name=video_path) if codec is None else codec
2532
2644
  if verbose:
2533
- print(
2534
- f"Resizing width video {video_name} (Video {cnt+1}/{len(video_paths)})..."
2535
- )
2645
+ stdout_information(msg=f"Resizing width video {video_name} (Video {cnt+1}/{len(video_paths)})...", source=resize_videos_by_height.__name__)
2536
2646
  if overwrite:
2537
2647
  dt = datetime.now().strftime("%Y%m%d%H%M%S")
2538
2648
  save_path = os.path.join(dir_name, f"{video_name}_{dt}.mp4")
@@ -2546,7 +2656,7 @@ def resize_videos_by_width(video_paths: List[Union[str, os.PathLike]],
2546
2656
  if gpu:
2547
2657
  cmd = f'ffmpeg -y -hwaccel auto -i "{video_path}" -vf "scale={width}:-2" -c:v h264_nvenc -rc vbr -cq {quality} "{save_path}" -hide_banner -loglevel error -stats -y'
2548
2658
  else:
2549
- cmd = f'ffmpeg -y -i "{video_path}" -vf scale={width}:-2 -crf {quality} "{save_path}" -hide_banner -loglevel error -stats -y'
2659
+ cmd = f'ffmpeg -y -i "{video_path}" -vf scale={width}:-2 -c:v {video_codec} -crf {quality} "{save_path}" -hide_banner -loglevel error -stats -y'
2550
2660
  subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
2551
2661
  if overwrite:
2552
2662
  shutil.copy(save_path, video_path)
@@ -2685,7 +2795,6 @@ def vertical_video_concatenator(video_paths: List[Union[str, os.PathLike]],
2685
2795
  :width: 300
2686
2796
  :align: center
2687
2797
 
2688
-
2689
2798
  .. seealso::
2690
2799
  :func:`simba.video_processors.video_processing.horizontal_video_concatenator`
2691
2800
 
@@ -2991,6 +3100,7 @@ def clip_videos_by_frame_ids(file_paths: List[Union[str, os.PathLike]],
2991
3100
  frm_ids: List[List[int]],
2992
3101
  save_dir: Optional[Union[str, os.PathLike]] = None,
2993
3102
  gpu: Optional[bool] = False,
3103
+ codec: Optional[str] = None,
2994
3104
  quality: Optional[int] = None):
2995
3105
 
2996
3106
  """
@@ -3033,13 +3143,14 @@ def clip_videos_by_frame_ids(file_paths: List[Union[str, os.PathLike]],
3033
3143
  video_timer = SimbaTimer(start=True)
3034
3144
  dir, video_name, ext = get_fn_ext(filepath=file_path)
3035
3145
  s_f, e_f = frm_ids[cnt][0], frm_ids[cnt][1]
3036
- print(f"Trimming {video_name} from frame {s_f} to frame {e_f} (Video {cnt+1}/{len(file_paths)})...")
3146
+ stdout_information(msg=f"Trimming {video_name} from frame {s_f} to frame {e_f} (Video {cnt+1}/{len(file_paths)})...", source=clip_videos_by_frame_ids.__name__)
3147
+ video_codec = get_ffmpeg_codec(file_name=file_path) if codec is None else codec
3037
3148
  if save_dir is not None:
3038
3149
  out_path = os.path.join(save_dir, os.path.basename(file_path))
3039
3150
  else:
3040
3151
  out_path = os.path.join(dir, f"{video_name}_{s_f}_{e_f}{ext}")
3041
3152
  if not gpu:
3042
- cmd = f'ffmpeg -i "{file_path}" -vf trim=start_frame={s_f}:end_frame={e_f} -an -crf {quality} "{out_path}" -loglevel error -stats -y'
3153
+ cmd = f'ffmpeg -i "{file_path}" -vf trim=start_frame={s_f}:end_frame={e_f} -c:v {video_codec} -an -crf {quality} "{out_path}" -loglevel error -stats -y'
3043
3154
  else:
3044
3155
  cmd = f'ffmpeg -hwaccel auto -i "{file_path}" -vf trim=start_frame={s_f}:end_frame={e_f} -c:v h264_nvenc -rc vbr -cq {quality} -an "{out_path}" -loglevel error -stats -y'
3045
3156
  subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
@@ -3309,6 +3420,7 @@ def convert_to_mov(path: Union[str, os.PathLike],
3309
3420
  def superimpose_video_progressbar(video_path: Union[str, os.PathLike],
3310
3421
  bar_height: Optional[int] = 10,
3311
3422
  color: Optional[str] = 'red',
3423
+ codec: Optional[str] = None,
3312
3424
  position: Optional[Literal['top', 'bottom']] = 'bottom',
3313
3425
  save_dir: Optional[Union[str, os.PathLike]] = None,
3314
3426
  gpu: Optional[bool] = False) -> None:
@@ -3345,28 +3457,30 @@ def superimpose_video_progressbar(video_path: Union[str, os.PathLike],
3345
3457
  video_path = find_all_videos_in_directory(directory=video_path, as_dict=True, raise_error=True)
3346
3458
  video_paths = list(video_path.values())
3347
3459
  else:
3348
- raise InvalidInputError(msg='{} is not a valid file path or directory path.', source=superimpose_video_progressbar.__name__)
3460
+ raise InvalidInputError(msg=f'{video_path} is not a valid file path or directory path.', source=superimpose_video_progressbar.__name__)
3349
3461
  if save_dir is not None:
3350
3462
  check_if_dir_exists(in_dir=save_dir)
3351
3463
  else:
3352
3464
  save_dir, _, _ = get_fn_ext(filepath=video_paths[0])
3465
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=superimpose_video_progressbar.__name__)
3353
3466
  for cnt, video_path in enumerate(video_paths):
3354
3467
  video_meta_data = get_video_meta_data(video_path=video_path)
3355
3468
  video_length = video_meta_data['video_length_s']
3356
3469
  width, height = video_meta_data['width'], video_meta_data['height']
3357
3470
  bar_height = int(height * (bar_height/100))
3358
3471
  _, video_name, ext = get_fn_ext(filepath=video_path)
3359
- print(f'Inserting progress bar on video {video_name}...')
3472
+ video_codec = get_ffmpeg_codec(file_name=video_path) if codec is None else codec
3473
+ stdout_information(msg=f'Inserting progress bar on video {video_name}...', source=superimpose_video_progressbar.__name__)
3360
3474
  save_path = os.path.join(save_dir, f'{video_name}_progress_bar{ext}')
3361
3475
  check_int(name=f'{superimpose_video_progressbar} height', value=bar_height, max_value=height, min_value=1)
3362
3476
  if position == 'bottom':
3363
3477
  if not gpu:
3364
- cmd = f'ffmpeg -i "{video_path}" -filter_complex "color=c={color}:s={width}x{bar_height}[bar];[0][bar]overlay=-w+(w/{video_length})*t:H-h:shortest=1" -c:a copy "{save_path}" -loglevel error -stats -hide_banner -y'
3478
+ cmd = f'ffmpeg -i "{video_path}" -filter_complex "color=c={color}:s={width}x{bar_height}[bar];[0][bar]overlay=-w+(w/{video_length})*t:H-h:shortest=1" -c:v {video_codec} -c:a copy "{save_path}" -loglevel error -stats -hide_banner -y'
3365
3479
  else:
3366
3480
  cmd = f'ffmpeg -hwaccel auto -i "{video_path}" -filter_complex "color=c={color}:s={width}x{bar_height}[bar];[0][bar]overlay=-w+(w/{video_length})*t:H-h:shortest=1" -c:v h264_nvenc -c:a copy "{save_path}" -loglevel error -stats -hide_banner -y'
3367
3481
  else:
3368
3482
  if not gpu:
3369
- cmd = f'ffmpeg -i "{video_path}" -filter_complex "color=c={color}:s={width}x{bar_height}[bar];[0][bar]overlay=-w+(w/{video_length})*t:{bar_height}-h:shortest=1" -c:a copy "{save_path}" -loglevel error -stats -hide_banner -y'
3483
+ cmd = f'ffmpeg -i "{video_path}" -filter_complex "color=c={color}:s={width}x{bar_height}[bar];[0][bar]overlay=-w+(w/{video_length})*t:{bar_height}-h:shortest=1" -c:a copy -c:v {video_codec} "{save_path}" -loglevel error -stats -hide_banner -y'
3370
3484
  else:
3371
3485
  cmd = f'ffmpeg -hwaccel auto -c:v h264_cuvid -i "{video_path}" -filter_complex "color=c={color}:s={width}x{bar_height}[bar];[0][bar]overlay=-w+(w/{video_length})*t:{bar_height}-h:shortest=1" -c:a copy "{save_path}" -loglevel error -stats -hide_banner -y'
3372
3486
  subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
@@ -3757,7 +3871,7 @@ def superimpose_elapsed_time(video_path: Union[str, os.PathLike],
3757
3871
 
3758
3872
  def reverse_videos(path: Union[str, os.PathLike],
3759
3873
  save_dir: Optional[Union[str, os.PathLike]] = None,
3760
- codec: Literal['libx265', 'libx264', 'vp9'] = 'libx265',
3874
+ codec: Optional[str] = None,
3761
3875
  quality: Optional[int] = 60,
3762
3876
  gpu: Optional[bool] = False) -> None:
3763
3877
 
@@ -3803,13 +3917,15 @@ def reverse_videos(path: Union[str, os.PathLike],
3803
3917
  os.makedirs(save_dir)
3804
3918
  else:
3805
3919
  raise InvalidInputError(msg=f'Path is not a valid file or directory path.', source=reverse_videos.__name__)
3920
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=reverse_videos.__name__)
3806
3921
  for file_cnt, file_path in enumerate(file_paths):
3807
3922
  _, video_name, ext = get_fn_ext(filepath=file_path)
3808
- print(f'Reversing video {video_name} (Video {file_cnt+1}/{len(file_paths)})...')
3923
+ stdout_information(msg=f'Reversing video {video_name} (Video {file_cnt+1}/{len(file_paths)})...', source=reverse_videos.__name__)
3924
+ video_codec = get_ffmpeg_codec(file_name=file_path) if codec is None else codec
3809
3925
  _ = get_video_meta_data(video_path=file_path)
3810
3926
  out_path = os.path.join(save_dir, f'{video_name}{ext}')
3811
3927
  if not gpu:
3812
- cmd = f'ffmpeg -i "{file_path}" -vf reverse -af areverse -c:v {codec} -crf {crf} "{out_path}" -loglevel error -stats -hide_banner -y'
3928
+ cmd = f'ffmpeg -i "{file_path}" -vf reverse -af areverse -c:v {video_codec} -crf {crf} "{out_path}" -loglevel error -stats -hide_banner -y'
3813
3929
  else:
3814
3930
  cmd = f'ffmpeg -hwaccel auto -i "{file_path}" -vf reverse -af areverse -c:v h264_nvenc -crf {crf} "{out_path}" -loglevel error -stats -hide_banner -y'
3815
3931
  subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
@@ -5080,7 +5196,7 @@ def change_playback_speed(video_path: Union[str, os.PathLike],
5080
5196
  quality: int = 60,
5081
5197
  gpu: bool = False,
5082
5198
  verbose: bool = True,
5083
- codec: str = 'libx264'):
5199
+ codec: Optional[str] = None):
5084
5200
  """
5085
5201
  Change the playback speed of a video file. Speed > 1.0 makes the video faster, speed < 1.0 makes it slower.
5086
5202
 
@@ -5123,6 +5239,10 @@ def change_playback_speed(video_path: Union[str, os.PathLike],
5123
5239
  else:
5124
5240
  save_path = os.path.join(dir, f'{video_name}_playback_speed_{speed}{ext}')
5125
5241
  video_pts = 1.0 / speed
5242
+ if codec is None:
5243
+ codec = get_ffmpeg_codec(file_name=video_path)
5244
+ else:
5245
+ check_valid_codec(codec=codec, raise_error=True, source=change_playback_speed.__name__)
5126
5246
  if gpu:
5127
5247
  cmd = f'ffmpeg -hwaccel auto -c:v h264_cuvid -i "{video_path}" -vf "setpts={video_pts:.6f}*PTS" -an -c:v h264_nvenc -rc vbr -cq {quality_code} "{save_path}" -hide_banner -loglevel error -stats -y'
5128
5248
  else:
@@ -5140,7 +5260,7 @@ def change_playback_speed_dir(data_dir: Union[str, os.PathLike],
5140
5260
  quality: int = 60,
5141
5261
  gpu: bool = False,
5142
5262
  verbose: bool = True,
5143
- codec: str = 'libx264'):
5263
+ codec: Optional[str] = None):
5144
5264
  """
5145
5265
  Change the playback speed of all video files in a directory. Speed > 1.0 makes videos faster, speed < 1.0 makes them slower.
5146
5266
 
@@ -5174,7 +5294,7 @@ def change_playback_speed_dir(data_dir: Union[str, os.PathLike],
5174
5294
  check_float(name=f'{change_playback_speed.__name__} speed', value=speed, min_value=0.000001, max_value=100, raise_error=True)
5175
5295
  check_int(name=f'{change_playback_speed.__name__} quality', value=quality, min_value=1, max_value=100, raise_error=True)
5176
5296
  check_if_dir_exists(in_dir=data_dir, source=f'{change_playback_speed.__name__} data_dir')
5177
- check_valid_codec(codec=codec, raise_error=True, source=change_playback_speed_dir.__name__)
5297
+ if codec is not None: check_valid_codec(codec=codec, raise_error=True, source=change_playback_speed_dir.__name__)
5178
5298
  video_dict = find_all_videos_in_directory(directory=data_dir, as_dict=True, raise_error=True, sort_alphabetically=True)
5179
5299
  total_video_cnt = len(list(video_dict.keys()))
5180
5300
  if save_dir is not None:
@@ -5186,9 +5306,10 @@ def change_playback_speed_dir(data_dir: Union[str, os.PathLike],
5186
5306
  raise DuplicationError(msg=f'The video directory and the save directory cannot be the same folder: {save_dir}', source=change_playback_speed_dir.__name__)
5187
5307
  for video_cnt, (video_name, video_path) in enumerate(video_dict.items()):
5188
5308
  _, _, ext = get_fn_ext(filepath=video_path)
5309
+ video_codec = get_ffmpeg_codec(file_name=video_path) if codec is None else codec
5189
5310
  save_path = os.path.join(save_dir, f'{video_name}{ext}')
5190
5311
  if verbose: print(f'Changing video speed for video {video_name} ({video_cnt+1}/{total_video_cnt})...')
5191
- change_playback_speed(video_path=video_path, speed=speed, save_path=save_path, quality=quality, codec=codec, verbose=False)
5312
+ change_playback_speed(video_path=video_path, speed=speed, save_path=save_path, quality=quality, codec=video_codec, verbose=False)
5192
5313
 
5193
5314
  timer.stop_timer()
5194
5315
  if verbose:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simba-uw-tf-dev
3
- Version: 4.7.1
3
+ Version: 4.7.5
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