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')
示例#2
0
    def parse_options(self, input_list, framerate, stats_file, downscale,
                      frame_skip, min_scene_len, drop_short_scenes):
        # type: (List[str], float, str, int, int) -> None
        """ Parse Options: Parses all global options/arguments passed to the main
        scenedetect command, before other sub-commands (e.g. this function processes
        the [options] when calling scenedetect [options] [commands [command options]].

        This method calls the _init_video_manager(), _open_stats_file(), and
        check_input_open() methods, which may raise a click.BadParameter exception.

        Raises:
            click.BadParameter
        """
        if not input_list:
            return

        self.logger.debug('Parsing program options.')

        self.frame_skip = frame_skip

        video_manager_initialized = self._init_video_manager(
            input_list=input_list, framerate=framerate, downscale=downscale)

        # Ensure VideoManager is initialized, and open StatsManager if --stats is specified.
        if not video_manager_initialized:
            self.video_manager = None
            self.logger.info('VideoManager not initialized.')
        else:
            self.logger.debug('VideoManager initialized.')
            self.stats_file_path = get_and_create_path(stats_file,
                                                       self.output_directory)
            if self.stats_file_path is not None:
                self.check_input_open()
                self._open_stats_file()

        # Init SceneManager.
        self.scene_manager = SceneManager(self.stats_manager)

        self.drop_short_scenes = drop_short_scenes
        self.min_scene_len = parse_timecode(self, min_scene_len)

        self.options_processed = True
示例#3
0
    def process_input(self):
        # type: () -> None
        """ Process Input: Processes input video(s) and generates output as per CLI commands.

        Run after all command line options/sub-commands have been parsed.
        """
        logging.debug('Processing input...')
        if not self.options_processed:
            logging.debug(
                'Skipping processing, CLI options were not parsed successfully.'
            )
            return
        self.check_input_open()
        if not self.scene_manager.get_num_detectors() > 0:
            logging.error(
                'No scene detectors specified (detect-content, detect-threshold, etc...),\n'
                '  or failed to process all command line arguments.')
            return

        # Handle scene detection commands (detect-content, detect-threshold, etc...).
        self.video_manager.start()
        base_timecode = self.video_manager.get_base_timecode()

        start_time = time.time()
        logging.info('Detecting scenes...')

        num_frames = self.scene_manager.detect_scenes(
            frame_source=self.video_manager,
            frame_skip=self.frame_skip,
            show_progress=not self.quiet_mode)

        duration = time.time() - start_time
        logging.info('Processed %d frames in %.1f seconds (average %.2f FPS).',
                     num_frames, duration,
                     float(num_frames) / duration)

        # Handle -s/--statsfile option.
        if self.stats_file_path is not None:
            if self.stats_manager.is_save_required():
                with open(self.stats_file_path, 'wt') as stats_file:
                    logging.info('Saving frame metrics to stats file: %s',
                                 os.path.basename(self.stats_file_path))
                    self.stats_manager.save_to_csv(stats_file, base_timecode)
            else:
                logging.debug(
                    'No frame metrics updated, skipping update of the stats file.'
                )

        # Get list of detected cuts and scenes from the SceneManager to generate the required output
        # files with based on the given commands (list-scenes, split-video, save-images, etc...).
        cut_list = self.scene_manager.get_cut_list(base_timecode)
        scene_list = self.scene_manager.get_scene_list(base_timecode)
        video_paths = self.video_manager.get_video_paths()
        video_name = os.path.basename(video_paths[0])
        if video_name.rfind('.') >= 0:
            video_name = video_name[:video_name.rfind('.')]

        # Ensure we don't divide by zero.
        if scene_list:
            logging.info(
                'Detected %d scenes, average shot length %.1f seconds.',
                len(scene_list),
                sum([(end_time - start_time).get_seconds()
                     for start_time, end_time in scene_list]) /
                float(len(scene_list)))
        else:
            logging.info('No scenes detected.')

        # Handle list-scenes command.
        if self.scene_list_output:
            scene_list_filename = Template(
                self.scene_list_name_format).safe_substitute(
                    VIDEO_NAME=video_name)
            if not scene_list_filename.lower().endswith('.csv'):
                scene_list_filename += '.csv'
            scene_list_path = get_and_create_path(
                scene_list_filename,
                self.scene_list_directory if self.scene_list_directory
                is not None else self.output_directory)
            logging.info('Writing scene list to CSV file:\n  %s',
                         scene_list_path)
            with open(scene_list_path, 'wt') as scene_list_file:
                write_scene_list(scene_list_file, scene_list, cut_list)
        # Handle `list-scenes`.
        if self.print_scene_list:
            logging.info(
                """Scene List:
-----------------------------------------------------------------------
 | Scene # | Start Frame |  Start Time  |  End Frame  |   End Time   |
-----------------------------------------------------------------------
%s
-----------------------------------------------------------------------
""", '\n'.join([
                    ' |  %5d  | %11d | %s | %11d | %s |' %
                    (i + 1, start_time.get_frames(), start_time.get_timecode(),
                     end_time.get_frames(), end_time.get_timecode())
                    for i, (start_time, end_time) in enumerate(scene_list)
                ]))

        if cut_list:
            logging.info('Comma-separated timecode list:\n  %s',
                         ','.join([cut.get_timecode() for cut in cut_list]))

        # Handle save-images command.
        if self.save_images:
            self._generate_images(
                scene_list=scene_list,
                video_name=video_name,
                image_name_template=self.image_name_format,
                output_dir=self.image_directory,
                downscale_factor=self.video_manager.get_downscale_factor())

        # Handle export-html command.
        if self.export_html:
            html_filename = Template(
                self.html_name_format).safe_substitute(VIDEO_NAME=video_name)
            if not html_filename.lower().endswith('.html'):
                html_filename += '.html'
            html_path = get_and_create_path(
                html_filename, self.image_directory
                if self.image_directory is not None else self.output_directory)
            logging.info('Exporting to html file:\n %s:', html_path)
            if not self.html_include_images:
                self.image_filenames = None
            write_scene_list_html(html_path,
                                  scene_list,
                                  cut_list,
                                  image_filenames=self.image_filenames,
                                  image_width=self.image_width,
                                  image_height=self.image_height)

        # Handle split-video command.
        if self.split_video:
            # Add proper extension to filename template if required.
            dot_pos = self.split_name_format.rfind('.')
            if self.split_mkvmerge and not self.split_name_format.endswith(
                    '.mkv'):
                self.split_name_format += '.mkv'
            # Don't add if we find an extension between 2 and 4 characters
            elif not (dot_pos >= 0) or (dot_pos >= 0
                                        and not ((len(self.split_name_format) -
                                                  (dot_pos + 1) <= 4 >= 2))):
                self.split_name_format += '.mp4'

            output_file_prefix = get_and_create_path(
                self.split_name_format, self.split_directory
                if self.split_directory is not None else self.output_directory)
            mkvmerge_available = is_mkvmerge_available()
            ffmpeg_available = is_ffmpeg_available()
            if mkvmerge_available and (self.split_mkvmerge
                                       or not ffmpeg_available):
                if not self.split_mkvmerge:
                    logging.warning(
                        'ffmpeg not found, falling back to fast copy mode (split-video -c/--copy).'
                    )
                split_video_mkvmerge(video_paths,
                                     scene_list,
                                     output_file_prefix,
                                     video_name,
                                     suppress_output=self.quiet_mode
                                     or self.split_quiet)
            elif ffmpeg_available:
                if self.split_mkvmerge:
                    logging.warning(
                        'mkvmerge not found, falling back to normal splitting'
                        ' mode (split-video).')
                split_video_ffmpeg(video_paths,
                                   scene_list,
                                   output_file_prefix,
                                   video_name,
                                   arg_override=self.split_args,
                                   hide_progress=self.quiet_mode,
                                   suppress_output=self.quiet_mode
                                   or self.split_quiet)
            else:
                if not (mkvmerge_available or ffmpeg_available):
                    error_strs = [
                        "ffmpeg/mkvmerge is required for split-video [-c/--copy]."
                    ]
                else:
                    error_strs = [
                        "{EXTERN_TOOL} is required for split-video{EXTRA_ARGS}."
                        .format(EXTERN_TOOL='mkvmerge'
                                if self.split_mkvmerge else 'ffmpeg',
                                EXTRA_ARGS=' -c/--copy'
                                if self.split_mkvmerge else '')
                    ]
                error_strs += [
                    "Install one of the above tools to enable the split-video command."
                ]
                error_str = '\n'.join(error_strs)
                logging.debug(error_str)
                raise click.BadParameter(error_str, param_hint='split-video')
            if scene_list:
                logging.info(
                    'Video splitting completed, individual scenes written to disk.'
                )
示例#4
0
    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.')
示例#5
0
    def process_input(self):
        # type: () -> None
        """ Process Input: Processes input video(s) and generates output as per CLI commands.

        Run after all command line options/sub-commands have been parsed.
        """
        self.logger.debug('Processing input...')
        if not self.options_processed:
            self.logger.debug(
                'Skipping processing, CLI options were not parsed successfully.'
            )
            return
        self.check_input_open()
        assert self.scene_manager.get_num_detectors() >= 0
        if self.scene_manager.get_num_detectors() == 0:
            self.logger.error(
                'No scene detectors specified (detect-content, detect-threshold, etc...),\n'
                ' or failed to process all command line arguments.')
            return

        # Display a warning if the video codec type seems unsupported (#86).
        if int(abs(self.video_manager.get(cv2.CAP_PROP_FOURCC))) == 0:
            self.logger.error(
                'Video codec detection failed, output may be incorrect.\nThis could be caused'
                ' by using an outdated version of OpenCV, or using codecs that currently are'
                ' not well supported (e.g. VP9).\n'
                'As a workaround, consider re-encoding the source material before processing.\n'
                'For details, see https://github.com/Breakthrough/PySceneDetect/issues/86'
            )

        # Handle scene detection commands (detect-content, detect-threshold, etc...).
        self.video_manager.start()

        start_time = time.time()
        self.logger.info('Detecting scenes...')

        num_frames = self.scene_manager.detect_scenes(
            frame_source=self.video_manager,
            frame_skip=self.frame_skip,
            show_progress=not self.quiet_mode)

        # Handle case where video fails with multiple audio tracks (#179).
        # TODO: Using a different video backend as per #213 may also resolve this issue,
        # as well as numerous other timing related issues.
        if num_frames <= 0:
            self.logger.critical(
                'Failed to read any frames from video file. This could be caused'
                ' by the video having multiple audio tracks. If so, please try'
                ' removing the audio tracks or muxing to mkv via:\n'
                '      ffmpeg -i input.mp4 -c copy -an output.mp4\n'
                'or:\n'
                '      mkvmerge -o output.mkv input.mp4\n'
                'For details, see https://pyscenedetect.readthedocs.io/en/latest/faq/'
            )
            return

        duration = time.time() - start_time
        self.logger.info(
            'Processed %d frames in %.1f seconds (average %.2f FPS).',
            num_frames, duration,
            float(num_frames) / duration)

        # Handle -s/--statsfile option.
        if self.stats_file_path is not None:
            if self.stats_manager.is_save_required():
                with open(self.stats_file_path, 'wt') as stats_file:
                    self.logger.info('Saving frame metrics to stats file: %s',
                                     os.path.basename(self.stats_file_path))
                    base_timecode = self.video_manager.get_base_timecode()
                    self.stats_manager.save_to_csv(stats_file, base_timecode)
            else:
                self.logger.debug(
                    'No frame metrics updated, skipping update of the stats file.'
                )

        # Get list of detected cuts and scenes from the SceneManager to generate the required output
        # files with based on the given commands (list-scenes, split-video, save-images, etc...).
        cut_list = self.scene_manager.get_cut_list()
        scene_list = self.scene_manager.get_scene_list()

        # Handle --drop-short-scenes.
        if self.drop_short_scenes and self.min_scene_len > 0:
            scene_list = [
                s for s in scene_list if (s[1] - s[0]) >= self.min_scene_len
            ]

        video_paths = self.video_manager.get_video_paths()
        video_name = self.video_manager.get_video_name()

        if scene_list:  # Ensure we don't divide by zero.
            self.logger.info(
                'Detected %d scenes, average shot length %.1f seconds.',
                len(scene_list),
                sum([(end_time - start_time).get_seconds()
                     for start_time, end_time in scene_list]) /
                float(len(scene_list)))
        else:
            self.logger.info('No scenes detected.')

        # Handle list-scenes command.
        if self.scene_list_output:
            scene_list_filename = Template(
                self.scene_list_name_format).safe_substitute(
                    VIDEO_NAME=video_name)
            if not scene_list_filename.lower().endswith('.csv'):
                scene_list_filename += '.csv'
            scene_list_path = get_and_create_path(
                scene_list_filename,
                self.scene_list_directory if self.scene_list_directory
                is not None else self.output_directory)
            self.logger.info('Writing scene list to CSV file:\n  %s',
                             scene_list_path)
            with open(scene_list_path, 'wt') as scene_list_file:
                write_scene_list(output_csv_file=scene_list_file,
                                 scene_list=scene_list,
                                 include_cut_list=not self.skip_cuts,
                                 cut_list=cut_list)

        if self.print_scene_list:
            self.logger.info(
                """Scene List:
-----------------------------------------------------------------------
 | Scene # | Start Frame |  Start Time  |  End Frame  |   End Time   |
-----------------------------------------------------------------------
%s
-----------------------------------------------------------------------
""", '\n'.join([
                    ' |  %5d  | %11d | %s | %11d | %s |' %
                    (i + 1, start_time.get_frames(), start_time.get_timecode(),
                     end_time.get_frames(), end_time.get_timecode())
                    for i, (start_time, end_time) in enumerate(scene_list)
                ]))

        if cut_list:
            self.logger.info(
                'Comma-separated timecode list:\n  %s',
                ','.join([cut.get_timecode() for cut in cut_list]))

        # Handle save-images command.

        if self.save_images:
            image_output_dir = self.output_directory
            if self.image_directory is not None:
                image_output_dir = self.image_directory

            image_filenames = save_images(
                scene_list=scene_list,
                video_manager=self.video_manager,
                num_images=self.num_images,
                frame_margin=self.frame_margin,
                image_extension=self.image_extension,
                encoder_param=self.image_param,
                image_name_template=self.image_name_format,
                output_dir=image_output_dir,
                show_progress=not self.quiet_mode,
                scale=self.scale,
                height=self.height,
                width=self.width)

        # Handle export-html command.
        if self.export_html:
            html_filename = Template(
                self.html_name_format).safe_substitute(VIDEO_NAME=video_name)
            if not html_filename.lower().endswith('.html'):
                html_filename += '.html'
            html_path = get_and_create_path(
                html_filename, self.image_directory
                if self.image_directory is not None else self.output_directory)
            self.logger.info('Exporting to html file:\n %s:', html_path)
            if not self.html_include_images:
                image_filenames = None
            write_scene_list_html(html_path,
                                  scene_list,
                                  cut_list,
                                  image_filenames=image_filenames,
                                  image_width=self.image_width,
                                  image_height=self.image_height)

        # Handle split-video command.
        if self.split_video:
            output_path_template = self.split_name_format
            # Add proper extension to filename template if required.
            dot_pos = output_path_template.rfind('.')
            extension_length = 0 if dot_pos < 0 else len(
                output_path_template) - (dot_pos + 1)
            # If using mkvmerge, force extension to .mkv.
            if self.split_mkvmerge and not output_path_template.endswith(
                    '.mkv'):
                output_path_template += '.mkv'
            # Otherwise, if using ffmpeg, only add an extension if one doesn't exist.
            elif not 2 <= extension_length <= 4:
                output_path_template += '.mp4'
            output_path_template = get_and_create_path(
                output_path_template, self.split_directory
                if self.split_directory is not None else self.output_directory)
            # Ensure the appropriate tool is available before handling split-video.
            check_split_video_requirements(self.split_mkvmerge)
            if self.split_mkvmerge:
                split_video_mkvmerge(video_paths,
                                     scene_list,
                                     output_path_template,
                                     video_name,
                                     suppress_output=self.quiet_mode
                                     or self.split_quiet)
            else:
                split_video_ffmpeg(video_paths,
                                   scene_list,
                                   output_path_template,
                                   video_name,
                                   arg_override=self.split_args,
                                   hide_progress=self.quiet_mode,
                                   suppress_output=self.quiet_mode
                                   or self.split_quiet)
            if scene_list:
                self.logger.info(
                    'Video splitting completed, individual scenes written to disk.'
                )
示例#6
0
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
示例#8
0
def save_bbox_to_file(start,
                      end,
                      fps,
                      tube_bbox,
                      frame_shape,
                      cur_traj,
                      inp,
                      image_shape,
                      vid_reader,
                      increase_area=0.1,
                      aspect_rat=True,
                      pan_thresh=None,
                      fa=None,
                      rm_low_res=None,
                      model=None,
                      device=None,
                      half=None):

    left, top, right, bot = tube_bbox
    width = right - left
    height = bot - top

    # Computing aspect preserving bbox
    width_increase = max(increase_area,
                         ((1 + 2 * increase_area) * height - width) /
                         (2 * width))
    height_increase = max(increase_area,
                          ((1 + 2 * increase_area) * width - height) /
                          (2 * height))

    left = int(left - width_increase * width)
    top = int(top - height_increase * height)
    right = int(right + width_increase * width)
    bot = int(bot + height_increase * height)

    top, bot, left, right = max(0, top), min(bot, frame_shape[0]), max(
        0, left), min(right, frame_shape[1])
    h, w = bot - top, right - left

    # making sure that h and w are the same
    if aspect_rat:
        if h != w:
            # change right and left boundaries of box if height is greater than width
            if h < w:
                dif = w - h
                if dif % 2 == 0:
                    right -= int(dif / 2)
                    left += int(dif / 2)
                else:
                    right -= (dif // 2) + 1
                    left += dif // 2
                h, w = bot - top, right - left
            # change top and bottom boundaries of box if width is greater than height,
            else:
                dif = h - w
                if dif % 2 == 0:
                    bot -= int(dif / 2)
                    top += int(dif / 2)
                else:
                    bot -= (dif // 2) + 1
                    top += dif // 2

                h, w = bot - top, right - left

    if rm_low_res:
        if h < rm_low_res and w < rm_low_res:
            return

    if pan_thresh:
        end_frame = vid_reader.get_data(end)
        start_frame = vid_reader.get_data(start)
        end_bbox = extract_bbox(end_frame, fa, model, device, half)
        start_bbox = extract_bbox(start_frame, fa, model, device, half)

        # get bboxes closest to tube bbox (that represents the bbox for the current trajetory)
        if len(start_bbox) == 0:
            return
        if len(start_bbox) == 1:
            start_bbox = start_bbox[0]
        else:
            max_iou = 0
            bbox_num = 0
            for i in range(len(start_bbox)):
                cur_iou = bb_intersection_over_union(tube_bbox, start_bbox[i])
                if cur_iou > max_iou:
                    max_iou = cur_iou
                    bbox_num = i
            start_bbox = start_bbox[bbox_num]
            print('         TUBE START', start_bbox)
        # same thing for ending frame
        if len(end_bbox) == 0:
            return
        if len(end_bbox) == 1:
            end_bbox = end_bbox[0]
        else:
            max_iou = 0
            bbox_num = 0
            for i in range(len(end_bbox)):
                cur_iou = bb_intersection_over_union(tube_bbox, end_bbox[i])
                if cur_iou < max_iou:
                    max_iou = cur_iou
                    bbox_num = i
            end_bbox = end_bbox[bbox_num]
            print('         TUBE END', end_bbox)

        if bb_intersection_over_union(end_bbox, start_bbox) < pan_thresh:
            return

        # end_start_iou = bb_intersection_over_union(end_bbox, start_bbox)
        # if end_start_iou > pan_thresh:
        #     return

    for frame in range(start, end):
        cur_frame = vid_reader.get_data(frame)
        # cropped_frame = cur_frame[top:bot, left:right, :]
        cropped_frame = cv2.cvtColor(cur_frame[top:bot, left:right, :],
                                     cv2.COLOR_RGB2BGR)
        if h > image_shape[0] and w > image_shape[1]:
            resized_frame = cv2.resize(cropped_frame,
                                       dsize=(image_shape[0], image_shape[1]),
                                       interpolation=cv2.INTER_AREA)
        else:
            resized_frame = cv2.resize(cropped_frame,
                                       dsize=(image_shape[0], image_shape[1]),
                                       interpolation=cv2.INTER_CUBIC)

        file_name = '{}-traj{}-frame{}.png'.format(args.name, cur_traj,
                                                   frame - start + 1)
        cv2.imwrite(
            get_and_create_path(
                file_name,
                args.out + '/' + args.name + '_traj' + str(cur_traj)),
            resized_frame)
示例#9
0
def scenedetect_cli(ctx, input, output, framerate, downscale, frame_skip,
                    min_scene_len, drop_short_scenes, stats, verbosity,
                    logfile, quiet):
    """ For example:

    scenedetect -i video.mp4 -s video.stats.csv detect-content list-scenes

    Note that the following options represent [OPTIONS] above. To list the optional
    [ARGS] for a particular COMMAND, type `scenedetect help COMMAND`. You can also
    combine commands (e.g. scenedetect [...] detect-content save-images --png split-video).


    """
    ctx.call_on_close(ctx.obj.process_input)
    logging.disable(logging.NOTSET)

    format_str = '[PySceneDetect] %(message)s'
    if verbosity.lower() == 'none':
        verbosity = None
    elif verbosity.lower() == 'debug':
        format_str = '%(levelname)s: %(module)s.%(funcName)s(): %(message)s'

    if quiet:
        verbosity = None

    ctx.obj.quiet_mode = True if verbosity is None else False
    ctx.obj.output_directory = output

    if logfile is not None:
        logfile = get_and_create_path(logfile)
        logging.basicConfig(filename=logfile,
                            filemode='a',
                            format=format_str,
                            level=getattr(logging, verbosity.upper())
                            if verbosity is not None else verbosity)
    elif verbosity is not None:
        logging.basicConfig(format=format_str,
                            level=getattr(logging, verbosity.upper()))
    else:
        logging.disable(logging.CRITICAL)

    logging.info('PySceneDetect %s', scenedetect.__version__)

    if stats is not None and frame_skip != 0:
        ctx.obj.options_processed = False
        error_strs = [
            'Unable to detect scenes with stats file if frame skip is not 1.',
            '  Either remove the -fs/--frame-skip option, or the -s/--stats file.\n'
        ]
        logging.error('\n'.join(error_strs))
        raise click.BadParameter(
            '\n  Combining the -s/--stats and -fs/--frame-skip options is not supported.',
            param_hint='frame skip + stats file')

    try:
        if ctx.obj.output_directory is not None:
            logging.info('Output directory set:\n  %s',
                         ctx.obj.output_directory)
        ctx.obj.parse_options(input_list=input,
                              framerate=framerate,
                              stats_file=stats,
                              downscale=downscale,
                              frame_skip=frame_skip,
                              min_scene_len=min_scene_len,
                              drop_short_scenes=drop_short_scenes)

    except Exception as ex:
        logging.error('Could not parse CLI options.: %s', ex)
        raise
示例#10
0
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
示例#11
0
    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(
                        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.')