def write_frames_of_scenes(video_path, output_dir, vid_name, l_scene_end): vid_read = get_reader(video_path) curr_scene = 1 frame_num = 0 for i, frame in tqdm(enumerate(vid_read)): if i in l_scene_end: curr_scene += 1 frame_num = 0 frame_num += 1 file_name = '{}-scene{}-frame{}.png'.format(vid_name, curr_scene, frame_num) cv2.imwrite( get_and_create_path(file_name, output_dir + '/scene' + str(curr_scene)), cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)) print('Completed writing files')
def save_images(scene_list, video_manager, num_images=3, frame_margin=1, image_extension='jpg', encoder_param=95, image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER', output_dir=None, downscale_factor=1, show_progress=False, scale=None, height=None, width=None): # type: (List[Tuple[FrameTimecode, FrameTimecode]], VideoManager, # Optional[int], Optional[int], Optional[str], Optional[int], # Optional[str], Optional[str], Optional[int], Optional[bool], # Optional[float], Optional[int], Optional[int]) # -> Dict[List[str]] """ Saves a set number of images from each scene, given a list of scenes and the associated video/frame source. Arguments: scene_list: A list of scenes (pairs of FrameTimecode objects) returned from calling a SceneManager's detect_scenes() method. video_manager: A VideoManager object corresponding to the scene list. Note that the video will be closed/re-opened and seeked through. num_images: Number of images to generate for each scene. Minimum is 1. frame_margin: Number of frames to pad each scene around the beginning and end (e.g. moves the first/last image into the scene by N frames). Can set to 0, but will result in some video files failing to extract the very last frame. image_extension: Type of image to save (must be one of 'jpg', 'png', or 'webp'). encoder_param: Quality/compression efficiency, based on type of image: 'jpg' / 'webp': Quality 0-100, higher is better quality. 100 is lossless for webp. 'png': Compression from 1-9, where 9 achieves best filesize but is slower to encode. image_name_template: Template to use when creating the images on disk. Can use the macros $VIDEO_NAME, $SCENE_NUMBER, and $IMAGE_NUMBER. The image extension is applied automatically as per the argument image_extension. output_dir: Directory to output the images into. If not set, the output is created in the working directory. downscale_factor: Integer factor to downscale images by. No filtering is currently done, only downsampling (thus requiring an integer). show_progress: If True, shows a progress bar if tqdm is installed. scale: Optional factor by which to rescale saved images.A scaling factor of 1 would not result in rescaling. A value <1 results in a smaller saved image, while a value >1 results in an image larger than the original. This value is ignored if either the height or width values are specified. height: Optional value for the height of the saved images. Specifying both the height and width will resize images to an exact size, regardless of aspect ratio. Specifying only height will rescale the image to that number of pixels in height while preserving the aspect ratio. width: Optional value for the width of the saved images. Specifying both the width and height will resize images to an exact size, regardless of aspect ratio. Specifying only width will rescale the image to that number of pixels wide while preserving the aspect ratio. Returns: Dict[List[str]]: Dictionary of the format { scene_num : [image_paths] }, where scene_num is the number of the scene in scene_list (starting from 1), and image_paths is a list of the paths to the newly saved/created images. Raises: ValueError: Raised if any arguments are invalid or out of range (e.g. if num_images is negative). """ if not scene_list: return {} if num_images <= 0 or frame_margin < 0: raise ValueError() # TODO: Validate that encoder_param is within the proper range. # Should be between 0 and 100 (inclusive) for jpg/webp, and 1-9 for png. imwrite_param = [get_cv2_imwrite_params()[image_extension], encoder_param] if encoder_param is not None else [] video_name = video_manager.get_video_name() # Reset video manager and downscale factor. video_manager.release() video_manager.reset() video_manager.set_downscale_factor(downscale_factor) video_manager.start() # Setup flags and init progress bar if available. completed = True logging.info('Generating output images (%d per scene)...', num_images) progress_bar = None if show_progress and tqdm: progress_bar = tqdm( total=len(scene_list) * num_images, unit='images', dynamic_ncols=True) filename_template = Template(image_name_template) scene_num_format = '%0' scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' image_num_format = '%0' image_num_format += str(math.floor(math.log(num_images, 10)) + 2) + 'd' timecode_list = dict() fps = scene_list[0][0].framerate timecode_list = [ [ FrameTimecode(int(f), fps=fps) for f in [ # middle frames a[len(a)//2] if (0 < j < num_images-1) or num_images == 1 # first frame else min(a[0] + frame_margin, a[-1]) if j == 0 # last frame else max(a[-1] - frame_margin, a[0]) # for each evenly-split array of frames in the scene list for j, a in enumerate(np.array_split(r, num_images)) ] ] for i, r in enumerate([ # pad ranges to number of images r if 1+r[-1]-r[0] >= num_images else list(r) + [r[-1]] * (num_images - len(r)) # create range of frames in scene for r in ( range(start.get_frames(), end.get_frames()) # for each scene in scene list for start, end in scene_list ) ]) ] image_filenames = {i: [] for i in range(len(timecode_list))} aspect_ratio = get_aspect_ratio(video_manager) if abs(aspect_ratio - 1.0) < 0.01: aspect_ratio = None for i, scene_timecodes in enumerate(timecode_list): for j, image_timecode in enumerate(scene_timecodes): video_manager.seek(image_timecode) ret_val, frame_im = video_manager.read() if ret_val: file_path = '%s.%s' % ( filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1), IMAGE_NUMBER=image_num_format % (j + 1), FRAME_NUMBER=image_timecode.get_frames()), image_extension) image_filenames[i].append(file_path) if aspect_ratio is not None: frame_im = cv2.resize( frame_im, (0, 0), fx=aspect_ratio, fy=1.0, interpolation=cv2.INTER_CUBIC) # Get frame dimensions prior to resizing or scaling frame_height = frame_im.shape[0] frame_width = frame_im.shape[1] # Figure out what kind of resizing needs to be done if height and width: frame_im = cv2.resize( frame_im, (width, height), interpolation=cv2.INTER_CUBIC) elif height and not width: factor = height / float(frame_height) width = int(factor * frame_width) frame_im = cv2.resize( frame_im, (width, height), interpolation=cv2.INTER_CUBIC) elif width and not height: factor = width / float(frame_width) height = int(factor * frame_height) frame_im = cv2.resize( frame_im, (width, height), interpolation=cv2.INTER_CUBIC) elif scale: frame_im = cv2.resize( frame_im, (0, 0), fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC) cv2.imwrite( get_and_create_path(file_path, output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) if not completed: logging.error('Could not generate all output images.') return image_filenames
def split_video_ffmpeg(input_video_paths, scene_list, output_file_template, video_name, arg_override='-c:v libx264 -preset fast -crf 21 -c:a copy', hide_progress=False, suppress_output=False): # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str], # Optional[str], Optional[bool]) -> None """ Calls the ffmpeg command on the input video(s), generating a new video for each scene based on the start/end timecodes. """ if not input_video_paths or not scene_list: return logging.info( 'Splitting input video%s using ffmpeg, output path template:\n %s', 's' if len(input_video_paths) > 1 else '', output_file_template) if len(input_video_paths) > 1: # TODO: Add support for splitting multiple/appended input videos. # https://trac.ffmpeg.org/wiki/Concatenate#samecodec # Requires generating a temporary file list for ffmpeg. logging.error( 'Sorry, splitting multiple appended/concatenated input videos with' ' ffmpeg is not supported yet. This feature will be added to a future' ' version of PySceneDetect. In the meantime, you can try using the' ' -c / --copy option with the split-video to use mkvmerge, which' ' generates less accurate output, but supports multiple input videos.') raise NotImplementedError() arg_override = arg_override.replace('\\"', '"') ret_val = None arg_override = arg_override.split(' ') filename_template = Template(output_file_template) scene_num_format = '%0' scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' try: progress_bar = None total_frames = scene_list[-1][1].get_frames() - scene_list[0][0].get_frames() if tqdm and not hide_progress: progress_bar = tqdm(total=total_frames, unit='frame', miniters=1) processing_start_time = time.time() for i, (start_time, end_time) in enumerate(scene_list): duration = (end_time - start_time) call_list = ['ffmpeg'] if suppress_output: call_list += ['-v', 'quiet'] elif i > 0: # Only show ffmpeg output for the first call, which will display any # errors if it fails, and then break the loop. We only show error messages # for the remaining calls. call_list += ['-v', 'error'] call_list += [ '-y', '-ss', start_time.get_timecode(), '-i', input_video_paths[0]] call_list += arg_override call_list += [ '-strict', '-2', '-t', duration.get_timecode(), '-sn', filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1)) ] ret_val = subprocess.call(call_list) if not suppress_output and i == 0 and len(scene_list) > 1: logging.info( 'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...') if ret_val != 0: break if progress_bar: progress_bar.update(duration.get_frames()) if progress_bar: print('') logging.info('Average processing speed %.2f frames/sec.', float(total_frames) / (time.time() - processing_start_time)) except OSError: logging.error('ffmpeg could not be found on the system.' ' Please install ffmpeg to enable video output support.') if ret_val is not None and ret_val != 0: logging.error('Error splitting video (ffmpeg returned %d).', ret_val)
def detect_scenes(self, frame_source, end_time=None, frame_skip=0, show_progress=True): # type: (VideoManager, Union[int, FrameTimecode], # Optional[Union[int, FrameTimecode]], Optional[bool]) -> int """ Perform scene detection on the given frame_source using the added SceneDetectors. Blocks until all frames in the frame_source have been processed. Results can be obtained by calling either the get_scene_list() or get_cut_list() methods. Arguments: frame_source (scenedetect.video_manager.VideoManager or cv2.VideoCapture): A source of frames to process (using frame_source.read() as in VideoCapture). VideoManager is preferred as it allows concatenation of multiple videos as well as seeking, by defining start time and end time/duration. end_time (int or FrameTimecode): Maximum number of frames to detect (set to None to detect all available frames). Only needed for OpenCV VideoCapture objects; for VideoManager objects, use set_duration() instead. frame_skip (int): Not recommended except for extremely high framerate videos. Number of frames to skip (i.e. process every 1 in N+1 frames, where N is frame_skip, processing only 1/N+1 percent of the video, speeding up the detection time at the expense of accuracy). `frame_skip` **must** be 0 (the default) when using a StatsManager. show_progress (bool): If True, and the ``tqdm`` module is available, displays a progress bar with the progress, framerate, and expected time to complete processing the video frame source. Returns: int: Number of frames read and processed from the frame source. Raises: ValueError: `frame_skip` **must** be 0 (the default) if the SceneManager was constructed with a StatsManager object. """ if frame_skip > 0 and self._stats_manager is not None: raise ValueError('frame_skip must be 0 when using a StatsManager.') start_frame = 0 curr_frame = 0 end_frame = None total_frames = math.trunc(frame_source.get(cv2.CAP_PROP_FRAME_COUNT)) start_time = frame_source.get(cv2.CAP_PROP_POS_FRAMES) if isinstance(start_time, FrameTimecode): start_frame = start_time.get_frames() elif start_time is not None: start_frame = int(start_time) self._start_frame = start_frame curr_frame = start_frame if isinstance(end_time, FrameTimecode): end_frame = end_time.get_frames() elif end_time is not None: end_frame = int(end_time) if end_frame is not None: total_frames = end_frame if start_frame is not None and not isinstance(start_time, FrameTimecode): total_frames -= start_frame if total_frames < 0: total_frames = 0 progress_bar = None if tqdm and show_progress: progress_bar = tqdm( total=total_frames, unit='frames') try: while True: if end_frame is not None and curr_frame >= end_frame: break # We don't compensate for frame_skip here as the frame_skip option # is not allowed when using a StatsManager - thus, processing is # *always* required for *all* frames when frame_skip > 0. if (self._is_processing_required(self._num_frames + start_frame) or self._is_processing_required(self._num_frames + start_frame + 1)): ret_val, frame_im = frame_source.read() else: ret_val = frame_source.grab() frame_im = None if not ret_val: break self._process_frame(self._num_frames + start_frame, frame_im) curr_frame += 1 self._num_frames += 1 if progress_bar: progress_bar.update(1) if frame_skip > 0: for _ in range(frame_skip): if not frame_source.grab(): break curr_frame += 1 self._num_frames += 1 if progress_bar: progress_bar.update(1) self._post_process(curr_frame) num_frames = curr_frame - start_frame finally: if progress_bar: progress_bar.close() return num_frames
def _generate_images(self, scene_list, video_name, image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER', output_dir=None): # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None if self.num_images != 2: raise NotImplementedError() if not scene_list: return if not self.options_processed: return self.check_input_open() imwrite_param = [] if self.image_param is not None: imwrite_param = [self.imwrite_params[self.image_extension], self.image_param] click.echo(imwrite_param) # Reset video manager and downscale factor. self.video_manager.release() self.video_manager.reset() self.video_manager.set_downscale_factor(1) self.video_manager.start() filename_template = Template(image_name_template) # Setup flags and init progress bar if available. completed = True logging.info('Generating output images (%d per scene)...', self.num_images) progress_bar = None if tqdm and not self.quiet_mode: progress_bar = tqdm( total=len(scene_list) * 2, unit='images') scene_num_format = '%0' scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' image_num_format = '%0' image_num_format += str(math.floor(math.log(self.num_images, 10)) + 2) + 'd' for i, (start_time, end_time) in enumerate(scene_list): # TODO: Interpolate timecodes if num_frames_per_scene != 2. self.video_manager.seek(start_time) self.video_manager.grab() ret_val, frame_im = self.video_manager.retrieve() if ret_val: cv2.imwrite( self.get_output_file_path( '%s.%s' % (filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i+1), IMAGE_NUMBER=image_num_format % (1) ), self.image_extension), output_dir=output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) self.video_manager.seek(end_time - 1) self.video_manager.grab() ret_val, frame_im = self.video_manager.retrieve() if ret_val: cv2.imwrite( self.get_output_file_path( '%s.%s' % (filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER='%03d' % (i+1), IMAGE_NUMBER='%02d' % (2) ), self.image_extension), output_dir=output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) if not completed: logging.error('Could not generate all output images.')
def generate_images( scene_list, video_manager, video_name, num_images=2, image_extension='jpg', quality_or_compression=95, image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER', output_dir=None, downscale_factor=1, show_progress=False): # type: (...) -> bool """ TODO: Documentation. Arguments: quality_or_compression: For image_extension=jpg or webp, represents encoding quality, from 0-100 (higher indicates better quality). For WebP, 100 indicates lossless. Default value in the CLI is 95 for JPEG, and 100 for WebP. If image_extension=png, represents the compression rate, from 0-9. Higher values produce smaller files but result in longer compression time. This setting does not affect image quality (lossless PNG), only file size. Default value in the CLI is 3. [default: 95] Returns: True if all requested images were generated & saved successfully, False otherwise. """ if not scene_list: return True if num_images <= 0: raise ValueError() imwrite_param = [] available_extensions = get_cv2_imwrite_params() if quality_or_compression is not None: if image_extension in available_extensions: imwrite_param = [ available_extensions[image_extension], quality_or_compression ] else: valid_extensions = str(list(available_extensions.keys())) raise RuntimeError( 'Invalid image extension, must be one of (case-sensitive): %s' % valid_extensions) # Reset video manager and downscale factor. video_manager.release() video_manager.reset() video_manager.set_downscale_factor(downscale_factor) video_manager.start() # Setup flags and init progress bar if available. completed = True progress_bar = None if tqdm and show_progress: progress_bar = tqdm(total=len(scene_list) * num_images, unit='images') filename_template = Template(image_name_template) scene_num_format = '%0' scene_num_format += str( max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' image_num_format = '%0' image_num_format += str(math.floor(math.log(num_images, 10)) + 2) + 'd' timecode_list = dict() for i in range(len(scene_list)): timecode_list[i] = [] if num_images == 1: for i, (start_time, end_time) in enumerate(scene_list): duration = end_time - start_time timecode_list[i].append(start_time + int(duration.get_frames() / 2)) else: middle_images = num_images - 2 for i, (start_time, end_time) in enumerate(scene_list): timecode_list[i].append(start_time) if middle_images > 0: duration = (end_time.get_frames() - 1) - start_time.get_frames() duration_increment = None duration_increment = int(duration / (middle_images + 1)) for j in range(middle_images): timecode_list[i].append(start_time + ((j + 1) * duration_increment)) # End FrameTimecode is always the same frame as the next scene's start_time # (one frame past the end), so we need to subtract 1 here. timecode_list[i].append(end_time - 1) for i in timecode_list: for j, image_timecode in enumerate(timecode_list[i]): video_manager.seek(image_timecode) video_manager.grab() ret_val, frame_im = video_manager.retrieve() if ret_val: file_path = '%s.%s' % (filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1), IMAGE_NUMBER=image_num_format % (j + 1)), image_extension) cv2.imwrite(get_and_create_path(file_path, output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) return completed
def process_video(args, scene_changes, crop_output='file', out_file=None, aspect_rat=True, pan_thresh=None, rm_low_res=128): device = 'cpu' if args.cpu else 'cuda' # fa = face_alignment.FaceAlignment(face_alignment.LandmarksType._2D, flip_input=False, # device=device, face_detector='blazeface') fa = None model, device, half = load_model(weights) video = imageio.get_reader(args.inp) trajectories = [] previous_frame = None fps = video.get_meta_data()['fps'] commands = [] num = 0 cur_traj = 0 try: # loop for each frame and i is frame num for i, frame in tqdm(enumerate(video)): # frame is matrix of pixels frame_shape = frame.shape # extract_bbox returns bboxes # bbox in this format: [bbox_num, [crop_x, crop_y, crop_x2, crop_y2, score]] bboxes = extract_bbox(frame, fa, model, device, half) # For each trajectory check the criterion not_valid_trajectories = [] valid_trajectories = [] # trajectory in this format: (bbox, tube_bbox, start frame, end frame) for trajectory in trajectories: # new tube_bbox is old bbox tube_bbox = trajectory[0] if trajectory[3] in scene_changes: # ending a trajectory if the frame is the frame of a scene change not_valid_trajectories.append(trajectory) continue intersection = 0 # bboxes in this format: [bbox_num, [crop_x, crop_y, crop_x2, crop_y2, score]] for bbox in bboxes: intersection = max( intersection, bb_intersection_over_union(tube_bbox, bbox)) if intersection > args.iou_with_initial: valid_trajectories.append(trajectory) else: not_valid_trajectories.append(trajectory) if crop_output == "file": cur_traj += save_bbox_traj_to_file(not_valid_trajectories, fps, frame_shape, video, cur_traj, args, aspect_rat, pan_thresh=pan_thresh, fa=fa, rm_low_res=rm_low_res, model=model, device=device, half=half) elif crop_output == 'ffmpeg cmd': # get ffmpeg commands to clip and crop vid for not valid trajectories, # bbox is valid up until this point, so get command for it new_cmd = compute_bbox_trajectories(not_valid_trajectories, fps, frame_shape, args) for cmd in new_cmd: commands.append(cmd) out_file.write("{} {}_{}.mp4\n".format( cmd, args.out_crops, num)) out_file.flush() num += 1 # continue on with valid trajectories trajectories = valid_trajectories ## Assign bbox to trajectories, create new trajectories for bbox in bboxes: intersection = 0 current_trajectory = None # trajectory in this format: (bbox, tube_bbox, start frame, end frame) for trajectory in trajectories: # new tube_bbox is old bbox tube_bbox = trajectory[0] # current_intersection is iou current_intersection = bb_intersection_over_union( tube_bbox, bbox) if intersection < current_intersection and current_intersection > args.iou_with_initial: intersection = bb_intersection_over_union( tube_bbox, bbox) current_trajectory = trajectory ## Create new trajectory if current_trajectory is None: # initial trajectory trajectories.append([bbox, bbox, i, i]) else: # change end frame to current frame current_trajectory[3] = i # join function combines tube_bbox and bbox to output new tube_bbox current_trajectory[1] = join(current_trajectory[1], bbox) except IndexError as e: raise (e) if crop_output == 'ffmpeg cmd': new_cmd = compute_bbox_trajectories(trajectories, fps, frame_shape, args) for cmd in new_cmd: commands.append(cmd) out_file.write("{} {}_{}.mp4\n".format(cmd, args.out_crops, num)) out_file.flush() num += 1 return commands
def detect_scenes(self, frame_source, start_time=0, end_time=None, frame_skip=0, show_progress=True): # type: (VideoManager, Union[int, FrameTimecode], # Optional[Union[int, FrameTimecode]], Optional[bool]) -> int """ Perform scene detection on the given frame_source using the added SceneDetectors. Blocks until all frames in the frame_source have been processed. Results can be obtained by calling the get_scene_list() method afterwards. Arguments: frame_source (scenedetect.VideoManager or cv2.VideoCapture): A source of frames to process (using frame_source.read() as in VideoCapture). VideoManager is preferred as it allows concatenation of multiple videos as well as seeking, by defining start time and end time/duration. start_time (int or FrameTimecode): Time/frame the passed frame_source object is currently at in time (i.e. the frame # read() will return next). Must be passed if the frame_source has been seeked past frame 0 (i.e. calling set_duration on a VideoManager or seeking a VideoCapture). end_time (int or FrameTimecode): Maximum number of frames to detect (set to None to detect all available frames). Only needed for OpenCV VideoCapture objects, as VideoManager allows set_duration. frame_skip (int): Number of frames to skip (i.e. process every 1 in N+1 frames, where N is frame_skip, processing only 1/N+1 percent of the video, speeding up the detection time at the expense of accuracy). show_progress (bool): If True, and the tqdm module is available, displays a progress bar with the progress, framerate, and expected time to complete processing the video frame source. Returns: Number of frames read and processed from the frame source. Raises: ValueError """ if frame_skip > 0 and self._stats_manager is not None: raise ValueError('frame_skip must be 0 when using a StatsManager.') start_frame = 0 curr_frame = 0 end_frame = None total_frames = math.trunc(frame_source.get(cv2.CAP_PROP_FRAME_COUNT)) if isinstance(start_time, FrameTimecode): start_frame = start_time.get_frames() elif start_time is not None: start_frame = int(start_time) self._start_frame = start_frame curr_frame = start_frame if isinstance(end_time, FrameTimecode): end_frame = end_time.get_frames() elif end_time is not None: end_frame = int(end_time) if end_frame is not None: total_frames = end_frame if start_frame is not None: total_frames -= start_frame progress_bar = None if tqdm and show_progress: progress_bar = tqdm(total=total_frames, unit='frames') try: while True: if end_frame is not None and curr_frame >= end_frame: break # We don't compensate for frame_skip here as the frame_skip option # is not allowed when using a StatsManager, and thus processing is # always required for all frames when using frame_skip. if (self._is_processing_required(self._num_frames + start_frame) or self._is_processing_required(self._num_frames + start_frame + 1)): ret_val, frame_im = frame_source.read() else: ret_val = frame_source.grab() frame_im = None if not ret_val: break self._process_frame(self._num_frames + start_frame, frame_im) curr_frame += 1 self._num_frames += 1 if progress_bar: progress_bar.update(1) if frame_skip > 0: for _ in range(frame_skip): if not frame_source.grab(): break curr_frame += 1 self._num_frames += 1 if progress_bar: progress_bar.update(1) self._post_process(curr_frame) num_frames = curr_frame - start_frame finally: if progress_bar: progress_bar.close() return num_frames
def _generate_images( self, scene_list, video_name, image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER', output_dir=None): # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None if not scene_list: return if not self.options_processed: return if self.num_images <= 0: raise ValueError() self.check_input_open() imwrite_param = [] if self.image_param is not None: imwrite_param = [ self.imwrite_params[self.image_extension], self.image_param ] # Reset video manager and downscale factor. self.video_manager.release() self.video_manager.reset() self.video_manager.set_downscale_factor(1) self.video_manager.start() # Setup flags and init progress bar if available. completed = True logging.info('Generating output images (%d per scene)...', self.num_images) progress_bar = None if tqdm and not self.quiet_mode: progress_bar = tqdm(total=len(scene_list) * self.num_images, unit='images') filename_template = Template(image_name_template) scene_num_format = '%0' scene_num_format += str( max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' image_num_format = '%0' image_num_format += str(math.floor(math.log(self.num_images, 10)) + 2) + 'd' timecode_list = dict() self.image_filenames = dict() for i in range(len(scene_list)): timecode_list[i] = [] self.image_filenames[i] = [] if self.num_images == 1: for i, (start_time, end_time) in enumerate(scene_list): duration = end_time - start_time timecode_list[i].append(start_time + int(duration.get_frames() / 2)) else: middle_images = self.num_images - 2 for i, (start_time, end_time) in enumerate(scene_list): timecode_list[i].append(start_time) if middle_images > 0: duration = (end_time.get_frames() - 1) - start_time.get_frames() duration_increment = None duration_increment = int(duration / (middle_images + 1)) for j in range(middle_images): timecode_list[i].append(start_time + ((j + 1) * duration_increment)) # End FrameTimecode is always the same frame as the next scene's start_time # (one frame past the end), so we need to subtract 1 here. timecode_list[i].append(end_time - 1) for i in timecode_list: for j, image_timecode in enumerate(timecode_list[i]): self.video_manager.seek(image_timecode) self.video_manager.grab() ret_val, frame_im = self.video_manager.retrieve() if ret_val: file_path = '%s.%s' % (filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1), IMAGE_NUMBER=image_num_format % (j + 1)), self.image_extension) self.image_filenames[i].append(file_path) cv2.imwrite( self.get_output_file_path(file_path, output_dir=output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) if not completed: logging.error('Could not generate all output images.')
def split_video_ffmpeg( input_video_paths, scene_list, output_file_template, video_name, arg_override='-c:v libx264 -preset fast -crf 21 -c:a aac', hide_progress=False, suppress_output=False): # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str], # Optional[str], Optional[bool], Optional[bool]) -> None """ Calls the ffmpeg command on the input video(s), generating a new video for each scene based on the start/end timecodes. Arguments: input_video_paths (List[str]): List of strings to the input video path(s). Is a list to allow for concatenation of multiple input videos together. scene_list (List[Tuple[FrameTimecode, FrameTimecode]]): List of scenes (pairs of FrameTimecodes) denoting the start/end frames of each scene. output_file_template (str): Template to use for generating the output filenames. Can use $VIDEO_NAME and $SCENE_NUMBER in this format, for example: `$VIDEO_NAME - Scene $SCENE_NUMBER` video_name (str): Name of the video to be substituted in output_file_template. arg_override (str): Allows overriding the arguments passed to ffmpeg for encoding. hide_progress (bool): If True, will hide progress bar provided by tqdm (if installed). suppress_output (bool): If True, will set verbosity to quiet for the first scene. """ if not input_video_paths or not scene_list: return logging.info( 'Splitting input video%s using ffmpeg, output path template:\n %s', 's' if len(input_video_paths) > 1 else '', output_file_template) if len(input_video_paths) > 1: # TODO: Add support for splitting multiple/appended input videos. # https://trac.ffmpeg.org/wiki/Concatenate#samecodec # Requires generating a temporary file list for ffmpeg. logging.error( 'Sorry, splitting multiple appended/concatenated input videos with' ' ffmpeg is not supported yet. This feature will be added to a future' ' version of PySceneDetect. In the meantime, you can try using the' ' -c / --copy option with the split-video to use mkvmerge, which' ' generates less accurate output, but supports multiple input videos.' ) raise NotImplementedError() arg_override = arg_override.replace('\\"', '"') ret_val = None arg_override = arg_override.split(' ') filename_template = Template(output_file_template) scene_num_format = '%0' scene_num_format += str( max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' try: progress_bar = None total_frames = scene_list[-1][1].get_frames( ) - scene_list[0][0].get_frames() if tqdm and not hide_progress: progress_bar = tqdm(total=total_frames, unit='frame', miniters=1) processing_start_time = time.time() final_file = filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1)) final_file = final_file.split('.')[0] if not os.path.isdir(f'{final_file}'): os.mkdir(f'{final_file}') for i, (start_time, end_time) in enumerate(scene_list): duration = (end_time - start_time) call_list = ['ffmpeg'] if suppress_output: call_list += ['-v', 'quiet'] elif i > 0: # Only show ffmpeg output for the first call, which will display any # errors if it fails, and then break the loop. We only show error messages # for the remaining calls. call_list += ['-v', 'error'] call_list += [ '-y', '-ss', str(start_time.get_seconds()), '-i', input_video_paths[0], '-t', str(duration.get_seconds()) ] call_list += arg_override call_list += ['-sn', f'{final_file}/%d.png'] ret_val = invoke_command(call_list) if not suppress_output and i == 0 and len(scene_list) > 1: logging.info( 'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...' ) if ret_val != 0: break if progress_bar: progress_bar.update(duration.get_frames()) if progress_bar: print('') logging.info( 'Average processing speed %.2f frames/sec.', float(total_frames) / (time.time() - processing_start_time)) except CommandTooLong: logging.error(COMMAND_TOO_LONG_STRING) except OSError: logging.error('ffmpeg could not be found on the system.' ' Please install ffmpeg to enable video output support.') if ret_val is not None and ret_val != 0: logging.error('Error splitting video (ffmpeg returned %d).', ret_val)
def generate_images( video_manager, scene_list, video_name, image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER', output_dir=None): import logging import cv2 from string import Template from scenedetect.platform import tqdm logging.getLogger().setLevel(logging.INFO) num_images = 2 image_extension = "jpg" # imwrite_param = [] # if image_param is not None: # imwrite_param = [imwrite_params[image_extension], image_param] # Reset video manager and downscale factor. video_manager = video_manager video_manager.release() video_manager.reset() video_manager.set_downscale_factor(1) video_manager.start() print("Detect Scenes:", len(scene_list)) # Setup flags and init progress bar if available. completed = True logging.info('Generating output images (%d per scene)...', num_images) progress_bar = None if tqdm: progress_bar = tqdm(total=len(scene_list) * num_images, unit='images') filename_template = Template(image_name_template) if scene_list: scene_num_format = '%0' scene_num_format += str( max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' image_num_format = '%0' image_num_format += str(math.floor(math.log(num_images, 10)) + 2) + 'd' timecode_list = dict() for i in range(len(scene_list)): timecode_list[i] = [] if num_images == 1: for i, (start_time, end_time) in enumerate(scene_list): duration = end_time - start_time timecode_list[i].append(start_time + int(duration.get_frames() / 2)) else: middle_images = num_images - 2 for i, (start_time, end_time) in enumerate(scene_list): timecode_list[i].append(start_time) if middle_images > 0: duration = (end_time.get_frames() - 1) - start_time.get_frames() duration_increment = None duration_increment = int(duration / (middle_images + 1)) for j in range(middle_images): timecode_list[i].append(start_time + ((j + 1) * duration_increment)) # End FrameTimecode is always the same frame as the next scene's start_time # (one frame past the end), so we need to subtract 1 here. timecode_list[i].append(end_time - 1) img_folder = create_folder(video_name) for i in timecode_list: for j, image_timecode in enumerate(timecode_list[i]): video_manager.seek(image_timecode) video_manager.grab() ret_val, frame_im = video_manager.retrieve() if ret_val: cv2.imwrite( get_output_file_path( '%s.%s' % (filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1), IMAGE_NUMBER=image_num_format % (j + 1)), image_extension), output_dir=os.path.join( os.path.dirname(os.path.realpath(__file__)), img_folder)), frame_im) # return output_dir else: completed = False break if progress_bar: progress_bar.update(1) # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None if not scene_list: img_folder = create_folder(video_name) video_manager.release() video_manager.reset() video_manager.set_downscale_factor(1) video_manager.start() video_manager.seek("00:00:02.000") video_manager.grab() ret_val, frame_im = video_manager.retrieve() if ret_val: cv2.imwrite( os.path.join(os.path.dirname(os.path.realpath(__file__)), img_folder, video_name[0:-4] + "." + image_extension), frame_im) # return os.path.join(os.path.dirname(os.path.realpath(__file__)), img_folder, video_name[0:-4]) if not completed: logging.error('Could not generate all output images.')
def _generate_images(self, scene_list, video_name, image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER', output_dir=None): # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None if not scene_list: return if not self.options_processed: return if self.num_images <= 0: raise ValueError() self.check_input_open() imwrite_param = [] if self.image_param is not None: imwrite_param = [self.imwrite_params[self.image_extension], self.image_param] # Reset video manager and downscale factor. self.video_manager.release() self.video_manager.reset() self.video_manager.set_downscale_factor(1) self.video_manager.start() # Setup flags and init progress bar if available. completed = True logging.info('Generating output images (%d per scene)...', self.num_images) progress_bar = None if tqdm and not self.quiet_mode: progress_bar = tqdm( total=len(scene_list) * self.num_images, unit='images') filename_template = Template(image_name_template) scene_num_format = '%0' scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' image_num_format = '%0' image_num_format += str(math.floor(math.log(self.num_images, 10)) + 2) + 'd' timecode_list = dict() self.image_filenames = dict() for i in range(len(scene_list)): timecode_list[i] = [] self.image_filenames[i] = [] if self.num_images == 1: for i, (start_time, end_time) in enumerate(scene_list): duration = end_time - start_time timecode_list[i].append(start_time + int(duration.get_frames() / 2)) else: middle_images = self.num_images - 2 for i, (start_time, end_time) in enumerate(scene_list): timecode_list[i].append(start_time) if middle_images > 0: duration = (end_time.get_frames() - 1) - start_time.get_frames() duration_increment = None duration_increment = int(duration / (middle_images + 1)) for j in range(middle_images): timecode_list[i].append(start_time + ((j+1) * duration_increment)) # End FrameTimecode is always the same frame as the next scene's start_time # (one frame past the end), so we need to subtract 1 here. timecode_list[i].append(end_time - 1) for i in timecode_list: for j, image_timecode in enumerate(timecode_list[i]): self.video_manager.seek(image_timecode) self.video_manager.grab() ret_val, frame_im = self.video_manager.retrieve() if ret_val: file_path = '%s.%s' % (filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1), IMAGE_NUMBER=image_num_format % (j + 1)), self.image_extension) self.image_filenames[i].append(file_path) cv2.imwrite( self.get_output_file_path(file_path, output_dir=output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) if not completed: logging.error('Could not generate all output images.')
def split_video_ffmpeg(input_video_paths, scene_list, output_file_prefix, arg_override='-c:v copy -c:a copy', output_extension='mp4', hide_progress=False, suppress_output=False): # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str], # Optional[str], Optional[bool]) -> None """ Calls the ffmpeg command on the input video(s), generating a new video for each scene based on the start/end timecodes. """ if not input_video_paths: return if len(input_video_paths) > 1: # TODO: Add support for splitting multiple/appended input videos. # https://trac.ffmpeg.org/wiki/Concatenate#samecodec # Requires generating a temporary file list for ffmpeg. logging.error('Splitting multiple input videos with ffmpeg is not supported yet.') raise NotImplementedError() arg_override = arg_override.replace('\\"', '"') ret_val = None arg_override = arg_override.split(' ') try: progress_bar = None if tqdm and not hide_progress: progress_bar = tqdm(total=len(scene_list), unit='scenes') for i, (start_time, end_time) in enumerate(scene_list): call_list = ['ffmpeg'] if suppress_output: call_list += ['-v', 'quiet'] elif i > 0: # Only show ffmpeg output for the first call, which will display any # errors if it fails. We suppress the output for the remaining calls. call_list += ['-v', 'error'] call_list += [ '-y', '-i', input_video_paths[0]] call_list += arg_override call_list += [ '-strict', '-2', '-ss', start_time.get_timecode(), '-t', (end_time - start_time).get_timecode(), '-sn', '%s-Scene-%03d.%s' % (output_file_prefix, i + 1, output_extension) ] ret_val = subprocess.call(call_list) if not suppress_output and i == 0 and len(scene_list) > 1: logging.info( 'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...') if ret_val != 0: break if progress_bar: progress_bar.update(1) except OSError: logging.error('ffmpeg could not be found on the system.' ' Please install ffmpeg to enable video output support.') if ret_val is not None and ret_val != 0: logging.error('Error splitting video (ffmpeg returned %d).', ret_val)
def detect_scenes(self, frame_source, end_time=None, frame_skip=0, show_progress=True, callback=None): # type: (VideoManager, Union[int, FrameTimecode], # Optional[Union[int, FrameTimecode]], Optional[bool], optional[callable[numpy.ndarray]) -> int """ Perform scene detection on the given frame_source using the added SceneDetectors. Blocks until all frames in the frame_source have been processed. Results can be obtained by calling either the get_scene_list() or get_cut_list() methods. Arguments: frame_source (scenedetect.video_manager.VideoManager or cv2.VideoCapture): A source of frames to process (using frame_source.read() as in VideoCapture). VideoManager is preferred as it allows concatenation of multiple videos as well as seeking, by defining start time and end time/duration. end_time (int or FrameTimecode): Maximum number of frames to detect (set to None to detect all available frames). Only needed for OpenCV VideoCapture objects; for VideoManager objects, use set_duration() instead. frame_skip (int): Not recommended except for extremely high framerate videos. Number of frames to skip (i.e. process every 1 in N+1 frames, where N is frame_skip, processing only 1/N+1 percent of the video, speeding up the detection time at the expense of accuracy). `frame_skip` **must** be 0 (the default) when using a StatsManager. show_progress (bool): If True, and the ``tqdm`` module is available, displays a progress bar with the progress, framerate, and expected time to complete processing the video frame source. callback ((image_ndarray, frame_num: int) -> None): If not None, called after each scene/event detected. Returns: int: Number of frames read and processed from the frame source. Raises: ValueError: `frame_skip` **must** be 0 (the default) if the SceneManager was constructed with a StatsManager object. """ if frame_skip > 0 and self._stats_manager is not None: raise ValueError('frame_skip must be 0 when using a StatsManager.') start_frame = 0 curr_frame = 0 end_frame = None self._base_timecode = FrameTimecode( timecode=0, fps=frame_source.get(cv2.CAP_PROP_FPS)) total_frames = math.trunc(frame_source.get(cv2.CAP_PROP_FRAME_COUNT)) start_time = frame_source.get(cv2.CAP_PROP_POS_FRAMES) if isinstance(start_time, FrameTimecode): start_frame = start_time.get_frames() elif start_time is not None: start_frame = int(start_time) self._start_frame = start_frame curr_frame = start_frame if isinstance(end_time, FrameTimecode): end_frame = end_time.get_frames() elif end_time is not None: end_frame = int(end_time) if end_frame is not None: total_frames = end_frame if start_frame is not None and not isinstance(start_time, FrameTimecode): total_frames -= start_frame if total_frames < 0: total_frames = 0 progress_bar = None if tqdm and show_progress: progress_bar = tqdm( total=total_frames, unit='frames', dynamic_ncols=True) try: while True: if end_frame is not None and curr_frame >= end_frame: break # We don't compensate for frame_skip here as the frame_skip option # is not allowed when using a StatsManager - thus, processing is # *always* required for *all* frames when frame_skip > 0. if (self._is_processing_required(self._num_frames + start_frame) or self._is_processing_required(self._num_frames + start_frame + 1)): ret_val, frame_im = frame_source.read() else: ret_val = frame_source.grab() frame_im = None if not ret_val: break self._process_frame(self._num_frames + start_frame, frame_im, callback) curr_frame += 1 self._num_frames += 1 if progress_bar: progress_bar.update(1) if frame_skip > 0: for _ in range(frame_skip): if not frame_source.grab(): break curr_frame += 1 self._num_frames += 1 if progress_bar: progress_bar.update(1) self._post_process(curr_frame) num_frames = curr_frame - start_frame finally: if progress_bar: progress_bar.close() return num_frames
def _generate_images(self, scene_list, image_prefix, output_dir=None): # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None if self.num_images != 2: raise NotImplementedError() if not scene_list: return if not self.options_processed: return self.check_input_open() imwrite_param = [] if self.image_param is not None: imwrite_param = [ self.imwrite_params[self.image_extension], self.image_param ] click.echo(imwrite_param) # Reset video manager and downscale factor. self.video_manager.release() self.video_manager.reset() self.video_manager.set_downscale_factor(1) self.video_manager.start() # Setup flags and init progress bar if available. completed = True logging.info('Generating output images (%d per scene)...', self.num_images) progress_bar = None if tqdm and not self.quiet_mode: progress_bar = tqdm(total=len(scene_list) * 2, unit='images') for i, (start_time, end_time) in enumerate(scene_list): # TODO: Interpolate timecodes if num_frames_per_scene != 2. self.video_manager.seek(start_time) self.video_manager.grab() ret_val, frame_im = self.video_manager.retrieve() if ret_val: cv2.imwrite( self.get_output_file_path( '%s-Scene-%03d-00.%s' % (image_prefix, i + 1, self.image_extension), output_dir=output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) self.video_manager.seek(end_time) self.video_manager.grab() ret_val, frame_im = self.video_manager.retrieve() if ret_val: cv2.imwrite( self.get_output_file_path( '%s-Scene-%03d-01.%s' % (image_prefix, i + 1, self.image_extension), output_dir=output_dir), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) if not completed: logging.error('Could not generate all output images.')
def _generate_images( self, scene_list, video_name, image_name_template='$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER', output_dir=None, downscale_factor=1): # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None if not scene_list: return if not self.options_processed: return if self.num_images <= 0: raise ValueError() self.check_input_open() imwrite_param = [] if self.image_param is not None: imwrite_param = [ self.imwrite_params[self.image_extension], self.image_param ] # Reset video manager and downscale factor. self.video_manager.release() self.video_manager.reset() self.video_manager.set_downscale_factor(1) self.video_manager.start() # Setup flags and init progress bar if available. completed = True logging.info('Generating output images (%d per scene)...', self.num_images) progress_bar = None if tqdm and not self.quiet_mode: progress_bar = tqdm(total=len(scene_list) * self.num_images, unit='images') filename_template = Template(image_name_template) scene_num_format = '%0' scene_num_format += str( max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' image_num_format = '%0' image_num_format += str(math.floor(math.log(self.num_images, 10)) + 2) + 'd' timecode_list = dict() fps = scene_list[0][0].framerate timecode_list = [ [ FrameTimecode(int(f), fps=fps) for f in [ # middle frames a[len(a) // 2] if ( 0 < j < self.num_images - 1) or self.num_images == 1 # first frame else min(a[0] + self.image_frame_margin, a[-1]) if j == 0 # last frame else max(a[-1] - self.image_frame_margin, a[0]) # for each evenly-split array of frames in the scene list for j, a in enumerate(np.array_split(r, self.num_images)) ] ] for i, r in enumerate([ # pad ranges to number of images r if r.stop - r.start >= self.num_images else list(r) + [r.stop - 1] * (self.num_images - len(r)) # create range of frames in scene for r in ( range(start.get_frames(), end.get_frames()) # for each scene in scene list for start, end in scene_list) ]) ] self.image_filenames = {i: [] for i in range(len(timecode_list))} for i, tl in enumerate(timecode_list): for j, image_timecode in enumerate(tl): self.video_manager.seek(image_timecode) self.video_manager.grab() ret_val, frame_im = self.video_manager.retrieve() if downscale_factor != 1: logging.info("resizing thumb") scale_percent = 1 / downscale_factor width = int(frame_im.shape[1] * scale_percent) height = int(frame_im.shape[0] * scale_percent) resized = cv2.resize(frame_im, (width, height), interpolation=cv2.INTER_AREA) frame_im = resized if ret_val: file_path = '%s.%s' % (filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1), IMAGE_NUMBER=image_num_format % (j + 1), FRAME_NUMBER=image_timecode.get_frames()), self.image_extension) self.image_filenames[i].append(file_path) cv2.imwrite( get_and_create_path( file_path, output_dir if output_dir is not None else self.output_directory), frame_im, imwrite_param) else: completed = False break if progress_bar: progress_bar.update(1) if not completed: logging.error('Could not generate all output images.')
def split_video_ffmpeg(input_video_paths, scene_list, output_file_template, video_name, arg_override='-c:v libx264 -preset fast -crf 21 -c:a copy', hide_progress=False, suppress_output=False): # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str], # Optional[str], Optional[bool]) -> None """ Calls the ffmpeg command on the input video(s), generating a new video for each scene based on the start/end timecodes. """ if not input_video_paths or not scene_list: return logging.info( 'Splitting input video%s using ffmpeg, output path template:\n %s', 's' if len(input_video_paths) > 1 else '', output_file_template) if len(input_video_paths) > 1: # TODO: Add support for splitting multiple/appended input videos. # https://trac.ffmpeg.org/wiki/Concatenate#samecodec # Requires generating a temporary file list for ffmpeg. logging.error( 'Sorry, splitting multiple appended/concatenated input videos with' ' ffmpeg is not supported yet. This feature will be added to a future' ' version of PySceneDetect. In the meantime, you can try using the' ' -c / --copy option with the split-video to use mkvmerge, which' ' generates less accurate output, but supports multiple input videos.') raise NotImplementedError() arg_override = arg_override.replace('\\"', '"') ret_val = None arg_override = arg_override.split(' ') filename_template = Template(output_file_template) scene_num_format = '%0' scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' try: progress_bar = None total_frames = scene_list[-1][1].get_frames() - scene_list[0][0].get_frames() if tqdm and not hide_progress: progress_bar = tqdm(total=total_frames, unit='frame', miniters=1) processing_start_time = time.time() for i, (start_time, end_time) in enumerate(scene_list): duration = (end_time - start_time) # Fix FFmpeg start timecode frame shift. start_time -= 1 call_list = ['ffmpeg'] if suppress_output: call_list += ['-v', 'quiet'] elif i > 0: # Only show ffmpeg output for the first call, which will display any # errors if it fails, and then break the loop. We only show error messages # for the remaining calls. call_list += ['-v', 'error'] call_list += [ '-y', '-ss', start_time.get_timecode(), '-i', input_video_paths[0]] call_list += arg_override call_list += [ '-strict', '-2', '-t', duration.get_timecode(), '-sn', filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1)) ] ret_val = subprocess.call(call_list) if not suppress_output and i == 0 and len(scene_list) > 1: logging.info( 'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...') if ret_val != 0: break if progress_bar: progress_bar.update(duration.get_frames()) if progress_bar: print('') logging.info('Average processing speed %.2f frames/sec.', float(total_frames) / (time.time() - processing_start_time)) except OSError: logging.error('ffmpeg could not be found on the system.' ' Please install ffmpeg to enable video output support.') if ret_val is not None and ret_val != 0: logging.error('Error splitting video (ffmpeg returned %d).', ret_val)