def __init__(self): # Properties for main scenedetect command options (-i, -s, etc...) and CliContext logic. self.options_processed = False # True when CLI option parsing is complete. self.scene_manager = None # detect-content, detect-threshold, etc... self.video_manager = None # -i/--input, -d/--downscale self.base_timecode = None # -f/--framerate self.start_frame = 0 # time -s/--start [start_frame] self.stats_manager = StatsManager() # -s/--stats self.stats_file_path = None # -s/--stats [stats_file_path] self.output_directory = None # -o/--output [output_directory] self.quiet_mode = False # -q/--quiet or -v/--verbosity quiet self.frame_skip = 0 # -fs/--frame-skip [frame_skip] # Properties for save-images command. self.save_images = False # save-images command self.image_extension = 'jpg' # save-images -j/--jpeg, -w/--webp, -p/--png self.image_directory = None # save-images -o/--output [image_directory] self.image_param = None # save-images -q/--quality if -j/-w, -c/--compression if -p self.num_images = 2 # save-images -n/--num-images self.imwrite_params = get_cv2_imwrite_params() # Properties for split-video command. self.split_video = False # split-video command self.split_mkvmerge = False # split-video -m/--mkvmerge (or split-video without ffmpeg) self.split_args = None # split-video -f/--ffmpeg-args [split_args] self.split_directory = None # split-video -o/--output [split_directory] self.split_quiet = False # split-video -q/--quiet # Properties for list-scenes command. self.list_scenes = False # list-scenes command self.print_scene_list = False # list-scenes --quiet/-q self.scene_list_path = None # list-scenes -o [scene_list_path]
def __init__(self): # Properties for main scenedetect command options (-i, -s, etc...) and CliContext logic. self.options_processed = False # True when CLI option parsing is complete. self.scene_manager = None # detect-content, detect-threshold, etc... self.video_manager = None # -i/--input, -d/--downscale self.base_timecode = None # -f/--framerate self.start_frame = 0 # time -s/--start self.stats_manager = StatsManager() # -s/--stats self.stats_file_path = None # -s/--stats self.output_directory = None # -o/--output self.quiet_mode = False # -q/--quiet or -v/--verbosity quiet self.frame_skip = 0 # -fs/--frame-skip # Properties for save-images command. self.save_images = False # save-images command self.image_extension = 'jpg' # save-images -j/--jpeg, -w/--webp, -p/--png self.image_directory = None # save-images -o/--output self.image_param = None # save-images -q/--quality if -j/-w, -c/--compression if -p self.image_name_format = '$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER' # save-images -f/--name-format self.num_images = 2 # save-images -n/--num-images self.imwrite_params = get_cv2_imwrite_params() # Properties for split-video command. self.split_video = False # split-video command self.split_mkvmerge = False # split-video -c/--copy self.split_args = None # split-video -a/--override-args self.split_directory = None # split-video -o/--output self.split_name_format = '$VIDEO_NAME-Scene-$SCENE_NUMBER' # split-video -f/--filename self.split_quiet = False # split-video -q/--quiet # Properties for list-scenes command. self.list_scenes = False # list-scenes command self.print_scene_list = False # list-scenes --quiet/-q self.scene_list_directory = None # list-scenes -o/--output self.scene_list_name_format = None # list-scenes -f/--filename self.scene_list_output = False # list-scenes -n/--no-output
def __init__(self): # Properties for main scenedetect command options (-i, -s, etc...) and CliContext logic. self.options_processed = False # True when CLI option parsing is complete. self.scene_manager = None # detect-content, detect-threshold, etc... self.video_manager = None # -i/--input, -d/--downscale self.base_timecode = None # -f/--framerate self.start_frame = 0 # time -s/--start self.stats_manager = None # -s/--stats self.stats_file_path = None # -s/--stats self.output_directory = None # -o/--output self.quiet_mode = False # -q/--quiet or -v/--verbosity quiet self.frame_skip = 0 # -fs/--frame-skip # Properties for save-images command. self.save_images = False # save-images command self.image_extension = 'jpg' # save-images -j/--jpeg, -w/--webp, -p/--png self.image_directory = None # save-images -o/--output self.image_param = None # save-images -q/--quality if -j/-w, # -c/--compression if -p self.image_name_format = ( # save-images -f/--name-format '$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER') self.num_images = 2 # save-images -n/--num-images self.imwrite_params = get_cv2_imwrite_params() # Properties for split-video command. self.split_video = False # split-video command self.split_mkvmerge = False # split-video -c/--copy self.split_args = None # split-video -a/--override-args self.split_directory = None # split-video -o/--output self.split_name_format = '$VIDEO_NAME-Scene-$SCENE_NUMBER' # split-video -f/--filename self.split_quiet = False # split-video -q/--quiet # Properties for list-scenes command. self.list_scenes = False # list-scenes command self.print_scene_list = False # list-scenes --quiet/-q self.scene_list_directory = None # list-scenes -o/--output self.scene_list_name_format = None # list-scenes -f/--filename self.scene_list_output = False # list-scenes -n/--no-output self.export_html = False # export-html command self.html_name_format = None # export-html -f/--filename self.html_include_images = True # export-html --no-images self.image_filenames = None # export-html used for embedding images self.image_width = None # export-html -w/--image-width self.image_height = None # export-html -h/--image-height
def save_images_command(self, num_images, output, name_format, jpeg, webp, quality, png, compression, frame_margin, scale, height, width): # type: (int, str, str, bool, bool, int, bool, int, float, int, int) -> None """ Save Images Command: Parses all options/arguments passed to the save-images command, or with respect to the CLI, this function processes [save-images options] when calling: scenedetect [global options] save-images [save-images options] [other commands...]. Raises: click.BadParameter """ self.check_input_open() if contains_sequence_or_url(self.video_manager.get_video_paths()): self.options_processed = False error_str = '\nThe save-images command is incompatible with image sequences/URLs.' self.logger.error(error_str) raise click.BadParameter(error_str, param_hint='save-images') num_flags = sum([1 if flag else 0 for flag in [jpeg, webp, png]]) if num_flags <= 1: # Ensure the format exists. extension = 'jpg' # Default is jpg. if png: extension = 'png' elif webp: extension = 'webp' valid_params = get_cv2_imwrite_params() if not extension in valid_params or valid_params[extension] is None: error_strs = [ 'Image encoder type %s not supported.' % extension.upper(), 'The specified encoder type could not be found in the current OpenCV module.', 'To enable this output format, please update the installed version of OpenCV.', 'If you build OpenCV, ensure the the proper dependencies are enabled. ' ] self.logger.debug('\n'.join(error_strs)) raise click.BadParameter('\n'.join(error_strs), param_hint='save-images') self.save_images = True self.image_directory = output self.image_extension = extension self.image_param = compression if png else quality self.image_name_format = name_format self.num_images = num_images self.frame_margin = frame_margin self.scale = scale self.height = height self.width = width image_type = 'JPEG' if self.image_extension == 'jpg' else self.image_extension.upper( ) image_param_type = '' if self.image_param: image_param_type = 'Compression' if image_type == 'PNG' else 'Quality' image_param_type = ' [%s: %d]' % (image_param_type, self.image_param) self.logger.info('Image output format set: %s%s', image_type, image_param_type) if self.image_directory is not None: self.logger.info('Image output directory set:\n %s', os.path.abspath(self.image_directory)) else: self.options_processed = False self.logger.error( 'Multiple image type flags set for save-images command.') raise click.BadParameter( 'Only one image type (JPG/PNG/WEBP) can be specified.', param_hint='save-images')
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 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 generate_images(cap, scene_list, num_images=1, video_name="testvid"): # type: (List[Tuple[FrameTimecode, FrameTimecode]) -> None if not scene_list: return imwrite_param = [] imwrite_param = [get_cv2_imwrite_params()['jpg'], None] #video_manager = VideoManager([url]) # Reset video manager and downscale factor. #video_manager.release() #video_manager.reset() #video_manager.set_downscale_factor(1) #video_manager.start() # Setup flags and init progress bar if available. completed = True 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() image_filenames = dict() for i in range(len(scene_list)): timecode_list[i] = [] image_filenames[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) urls = [] #image_timecode = "00:00:06.480" #dt_obj = datetime.strptime(str(image_timecode),'%H:%M:%S.%f') #millisec = dt_obj.timestamp() * 1000 #print(str(image_timecode) + str(millisec)) # Let's use Amazon S3 s3 = boto3.client('s3', aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"), aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY")) #for key in s3.list_objects(Bucket='motion-snapshots')['Contents']: #print(key['Key']) #urls.append(str(key['Key'])) try: for i in timecode_list: for j, image_timecode in enumerate(timecode_list[i]): cap.set(cv2.CAP_PROP_POS_MSEC, get_timestamp_to_milliseconds(str(image_timecode))) #cap.set(cv2.CAP_PROP_POS_MSEC,image_timecode.get_frames()) #urls.append(str(image_timecode)) #video_manager.seek(image_timecode) #video_manager.grab() #ret_val, frame_im = video_manager.retrieve() ret, frame = cap.read( ) # Retrieves the frame at the specified second if ret: imageName = randomword(10) + ".jpg" image_string = cv2.imencode('.jpg', frame)[1].tostring() s3.put_object(Bucket="motion-snapshots", Key="images/" + imageName, Body=image_string, ACL='public-read', ContentType='image/jpeg') #location = boto3.client('s3').get_bucket_location(Bucket="motion-snapshots")['LocationConstraint'] url = "https://s3-%s.amazonaws.com/%s/%s" % ( "us-east-2", "motion-snapshots", "images/" + imageName) #url = "https://s3-%s.amazonaws.com/%s/%s" % (location, "motion-snapshots", "images/" + imageName) print("Saved Image: " + str(url)) urls.append(url) else: completed = False break #if not completed: #logging.error('Could not generate all output images.') #except WritingError as er: #logging.error(er) finally: return urls
def find_scenes(video_path, generate_images=False): """ This method slicing a video to a list of scenes, each scene will have a similar color distributions. This function allows to generate images for each scene. :param video_path: The path to the video for finding scenes :param generate_images: whether to generate images or not :return: a list of scenes """ video_manager = VideoManager([video_path]) stats_manager = StatsManager() scene_manager = SceneManager(stats_manager) scene_manager.add_detector(ContentDetector()) base_timecode = video_manager.get_base_timecode() stats_file_path = '%s.stats.csv' % video_path scene_list = [] output = [] try: if os.path.exists(stats_file_path): with open(stats_file_path, 'r') as stats_file: stats_manager.load_from_csv(stats_file, base_timecode) video_manager.set_downscale_factor() video_manager.start() scene_manager.detect_scenes(frame_source=video_manager) scene_list = scene_manager.get_scene_list(base_timecode) print("Starting to generate images from scenelist") num_images = 2 if not scene_list: return available_extensions = get_cv2_imwrite_params() image_extension = "jpg" imwrite_param = [available_extensions[image_extension], 100] video_manager.release() video_manager.reset() video_manager.set_downscale_factor(1) video_manager.start() completed = True print('Generating output images (%d per scene)...', num_images) filename_template = Template( "$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER") 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 [ a[len(a) // 2] if (0 < j < num_images - 1) or num_images == 1 else min(a[0] + 0, a[-1]) if j == 0 else max(a[-1] - 0, a[0]) for j, a in enumerate(np.array_split(r, num_images)) ] ] for i, r in enumerate([ r if r.stop - r.start >= num_images else list(r) + [r.stop - 1] * (num_images - len(r)) for r in (range(start.get_frames(), end.get_frames()) for start, end in scene_list) ])] image_filenames = {i: [] for i in range(len(timecode_list))} for i, tl in enumerate(timecode_list): for j, image_timecode in enumerate(tl): 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_path, 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) abs_file_path = get_and_create_path(file_path, "output") output.append(frame_im) if generate_images: print(abs_file_path) cv2.imwrite(abs_file_path, frame_im, imwrite_param) else: completed = False break if not completed: print('Could not generate all output images.') finally: video_manager.release() return output