@@ -205,57 +205,63 @@ def get_project_image_count(project, with_all_subfolders=False):
205205 return len (search_images_all_folders (project ))
206206
207207
208- def upload_video_to_project (
209- project ,
210- video_path ,
211- target_fps = None ,
212- start_time = 0.0 ,
213- end_time = None ,
214- annotation_status = "NotStarted" ,
215- image_quality_in_editor = None
216- ):
217- """Uploads image frames from video to platform. Uploaded images will have
218- names "<video_name>_<frame_no>.jpg".
208+ def _get_video_frames_count (video_path ):
209+ """
210+ Get video frames count
211+ """
212+ video = cv2 .VideoCapture (str (video_path ), cv2 .CAP_FFMPEG )
213+ total_num_of_frames = int (video .get (cv2 .CAP_PROP_FRAME_COUNT ))
214+ if total_num_of_frames < 0 :
215+ total_num_of_frames = 0
216+ flag = True
217+ while flag :
218+ flag , _ = video .read ()
219+ if flag :
220+ total_num_of_frames += 1
221+ else :
222+ break
223+ return total_num_of_frames
219224
220- :param project: project name or folder path (e.g., "project1/folder1")
221- :type project: str
222- :param video_path: video to upload
223- :type video_path: Pathlike (str or Path)
224- :param target_fps: how many frames per second need to extract from the video (approximate).
225- If None, all frames will be uploaded
226- :type target_fps: float
227- :param start_time: Time (in seconds) from which to start extracting frames
228- :type start_time: float
229- :param end_time: Time (in seconds) up to which to extract frames. If None up to end
230- :type end_time: float
231- :param annotation_status: value to set the annotation statuses of the uploaded
232- video frames NotStarted InProgress QualityCheck Returned Completed Skipped
233- :type annotation_status: str
234- :param image_quality_in_editor: image quality be seen in SuperAnnotate web annotation editor.
235- Can be either "compressed" or "original". If None then the default value in project settings will be used.
236- :type image_quality_in_editor: str
237225
238- :return: filenames of uploaded images
239- :rtype: list of strs
226+ def _get_video_fps_ration (target_fps ,video ,ratio ):
240227 """
241- project , project_folder = get_project_and_folder_metadata (project )
242- upload_state = common .upload_state_int_to_str (project .get ("upload_state" ))
243- if upload_state == "External" :
244- raise SABaseException (
245- 0 ,
246- "The function does not support projects containing images attached with URLs"
228+ Get video fps / target fps ratio
229+ """
230+ video_fps = float (video .get (cv2 .CAP_PROP_FPS ))
231+ if target_fps >= video_fps :
232+ logger .warning (
233+ "Video frame rate %s smaller than target frame rate %s. Cannot change frame rate." ,
234+ video_fps , target_fps
235+ )
236+ else :
237+ logger .info (
238+ "Changing video frame rate from %s to target frame rate %s." ,
239+ video_fps , target_fps
247240 )
248- logger .info ("Uploading from video %s." , str (video_path ))
241+ ratio = video_fps / target_fps
242+ return ratio
243+
244+ def _get_available_image_counts (project ,folder ):
245+ if folder :
246+ folder_id = folder ["id" ]
247+ else :
248+ folder_id = get_project_root_folder_id (project )
249+ params = {'team_id' : project ['team_id' ] , 'folder_id' : folder_id }
250+ res = _get_upload_auth_token (params = params ,project_id = project ['id' ])
251+ return res ['availableImageCount' ]
252+
253+ def _get_video_rotate_code (video_path ):
249254 rotate_code = None
250255 try :
256+ cv2_rotations = {
257+ 90 : cv2 .ROTATE_90_CLOCKWISE ,
258+ 180 : cv2 .ROTATE_180 ,
259+ 270 :cv2 .ROTATE_90_COUNTERCLOCKWISE ,
260+ }
261+
251262 meta_dict = ffmpeg .probe (str (video_path ))
252263 rot = int (meta_dict ['streams' ][0 ]['tags' ]['rotate' ])
253- if rot == 90 :
254- rotate_code = cv2 .ROTATE_90_CLOCKWISE
255- elif rot == 180 :
256- rotate_code = cv2 .ROTATE_180
257- elif rot == 270 :
258- rotate_code = cv2 .ROTATE_90_COUNTERCLOCKWISE
264+ rotate_code = cv2_rotations [rot ]
259265 if rot != 0 :
260266 logger .info (
261267 "Frame rotation of %s found. Output images will be rotated accordingly." ,
@@ -269,61 +275,30 @@ def upload_video_to_project(
269275 "Couldn't read video metadata to determine rotation. %s" ,
270276 warning_str
271277 )
278+ return rotate_code
272279
273- video = cv2 .VideoCapture (str (video_path ), cv2 .CAP_FFMPEG )
274- if not video .isOpened ():
275- raise SABaseException (0 , "Couldn't open video file " + str (video_path ))
276-
277- total_num_of_frames = int (video .get (cv2 .CAP_PROP_FRAME_COUNT ))
278- if total_num_of_frames < 0 :
279- total_num_of_frames = 0
280- flag = True
281- while flag :
282- flag , frame = video .read ()
283- if flag :
284- total_num_of_frames += 1
285- else :
286- break
287- video = cv2 .VideoCapture (str (video_path ), cv2 .CAP_FFMPEG )
288- logger .info ("Video frame count is %s." , total_num_of_frames )
289-
290- r = 1.0
291- if target_fps is not None :
292- video_fps = float (video .get (cv2 .CAP_PROP_FPS ))
293- if target_fps >= video_fps :
294- logger .warning (
295- "Video frame rate %s smaller than target frame rate %s. Cannot change frame rate." ,
296- video_fps , target_fps
297- )
298- else :
299- logger .info (
300- "Changing video frame rate from %s to target frame rate %s." ,
301- video_fps , target_fps
302- )
303- r = video_fps / target_fps
304-
305- zero_fill_count = len (str (total_num_of_frames ))
306- tempdir = tempfile .TemporaryDirectory ()
307280
281+ def _extract_frames_from_video (start_time ,end_time ,ratio ,video ,video_path ,tempdir ,limit ,rotate_code ,total_num_of_frames ):
308282 video_name = Path (video_path ).stem
309283 frame_no = 0
310284 frame_no_with_change = 1.0
311285 extracted_frame_no = 1
312286 logger .info ("Extracting frames from video to %s." , tempdir .name )
313- while True :
287+ zero_fill_count = len (str (total_num_of_frames ))
288+ while extracted_frame_no < (limit + 1 ) :
314289 success , frame = video .read ()
315290 if not success :
316291 break
317292 frame_no += 1
318293 if round (frame_no_with_change ) != frame_no :
319294 continue
320- frame_no_with_change += r
295+ frame_no_with_change += ratio
321296 frame_time = video .get (cv2 .CAP_PROP_POS_MSEC ) / 1000.0
322- if end_time is not None and frame_time > end_time :
297+ if end_time and frame_time > end_time :
323298 break
324299 if frame_time < start_time :
325300 continue
326- if rotate_code is not None :
301+ if rotate_code :
327302 frame = cv2 .rotate (frame , rotate_code )
328303 cv2 .imwrite (
329304 str (
@@ -334,26 +309,79 @@ def upload_video_to_project(
334309 ), frame
335310 )
336311 extracted_frame_no += 1
312+ return extracted_frame_no - 1
313+
314+
315+ def upload_video_to_project (
316+ project ,
317+ video_path ,
318+ target_fps = None ,
319+ start_time = 0.0 ,
320+ end_time = None ,
321+ annotation_status = "NotStarted" ,
322+ image_quality_in_editor = None
323+ ):
324+ """Uploads image frames from video to platform. Uploaded images will have
325+ names "<video_name>_<frame_no>.jpg".
326+
327+ :param project: project name or folder path (e.g., "project1/folder1")
328+ :type project: str
329+ :param video_path: video to upload
330+ :type video_path: Pathlike (str or Path)
331+ :param target_fps: how many frames per second need to extract from the video (approximate).
332+ If None, all frames will be uploaded
333+ :type target_fps: float
334+ :param start_time: Time (in seconds) from which to start extracting frames
335+ :type start_time: float
336+ :param end_time: Time (in seconds) up to which to extract frames. If None up to end
337+ :type end_time: float
338+ :param annotation_status: value to set the annotation statuses of the uploaded
339+ video frames NotStarted InProgress QualityCheck Returned Completed Skipped
340+ :type annotation_status: str
341+ :param image_quality_in_editor: image quality be seen in SuperAnnotate web annotation editor.
342+ Can be either "compressed" or "original". If None then the default value in project settings will be used.
343+ :type image_quality_in_editor: str
344+
345+ :return: filenames of uploaded images
346+ :rtype: list of strs
347+ """
348+
349+ project , folder = get_project_and_folder_metadata (project )
350+ limit = _get_available_image_counts (project ,folder )
351+
352+ upload_state = common .upload_state_int_to_str (project .get ("upload_state" ))
353+ if upload_state == "External" :
354+ raise SABaseException (
355+ 0 ,
356+ "The function does not support projects containing images attached with URLs"
357+ )
358+ logger .info ("Uploading from video %s." , str (video_path ))
359+ rotate_code = _get_video_rotate_code (video_path )
360+ video = cv2 .VideoCapture (str (video_path ), cv2 .CAP_FFMPEG )
361+ if not video .isOpened ():
362+ raise SABaseException (0 , "Couldn't open video file " + str (video_path ))
337363
364+ total_num_of_frames = _get_video_frames_count (video_path )
365+ logger .info ("Video frame count is %s." , total_num_of_frames )
366+ ratio = 1.0
367+ if target_fps :
368+ ratio = _get_video_fps_ration (target_fps ,video ,ratio )
369+ tempdir = tempfile .TemporaryDirectory ()
370+ extracted_frame_no = _extract_frames_from_video (start_time ,end_time ,ratio ,
371+ video ,video_path ,tempdir ,
372+ limit ,rotate_code ,total_num_of_frames )
338373 logger .info (
339374 "Extracted %s frames from video. Now uploading to platform." ,
340- extracted_frame_no - 1
375+ extracted_frame_no
341376 )
342-
343377 filenames = upload_images_from_folder_to_project (
344- (project , project_folder ),
378+ (project , folder ),
345379 tempdir .name ,
346380 extensions = ["jpg" ],
347381 annotation_status = annotation_status ,
348382 image_quality_in_editor = image_quality_in_editor
349383 )
350-
351- assert len (filenames [1 ]) == 0
352-
353- filenames_base = []
354- for file in filenames [0 ]:
355- filenames_base .append (Path (file ).name )
356-
384+ filenames_base = [Path (f ).name for f in filenames [0 ]]
357385 return filenames_base
358386
359387
@@ -398,7 +426,7 @@ def upload_videos_from_folder_to_project(
398426 :return: uploaded and not-uploaded video frame images' filenames
399427 :rtype: tuple of list of strs
400428 """
401- project , project_folder = get_project_and_folder_metadata (project )
429+ project , folder = get_project_and_folder_metadata (project )
402430 upload_state = common .upload_state_int_to_str (project .get ("upload_state" ))
403431 if upload_state == "External" :
404432 raise SABaseException (
@@ -440,7 +468,7 @@ def upload_videos_from_folder_to_project(
440468 filenames = []
441469 for path in filtered_paths :
442470 filenames += upload_video_to_project (
443- (project , project_folder ),
471+ (project , folder ),
444472 path ,
445473 target_fps = target_fps ,
446474 start_time = start_time ,
0 commit comments