예제 #1
0
def split_video_command(ctx, output, filename, high_quality, override_args, quiet, copy,
                        rate_factor, preset):
    """Split input video(s) using ffmpeg or mkvmerge."""
    if ctx.obj.split_video:
        logging.warning('split-video command is specified twice.')
    ctx.obj.check_input_open()
    ctx.obj.split_video = True
    ctx.obj.split_quiet = True if quiet else False
    ctx.obj.split_directory = output
    ctx.obj.split_name_format = filename
    if copy:
        ctx.obj.split_mkvmerge = True
        if high_quality:
            logging.warning('-hq/--high-quality flag ignored due to -c/--copy.')
        if override_args:
            logging.warning('-f/--ffmpeg-args option ignored due to -c/--copy.')
    if not override_args:
        if rate_factor is None:
            rate_factor = 22 if not high_quality else 17
        if preset is None:
            preset = 'veryfast' if not high_quality else 'slow'
        override_args = ('-c:v libx264 -preset {PRESET} -crf {RATE_FACTOR} -c:a copy'.format(
            PRESET=preset, RATE_FACTOR=rate_factor))
    if not copy:
        logging.info('FFmpeg codec args set: %s', override_args)
    if filename:
        logging.info('Video output file name format: %s', filename)
    if ctx.obj.split_directory is not None:
        logging.info('Video output path set:  \n%s', ctx.obj.split_directory)
    ctx.obj.split_args = override_args

    mkvmerge_available = is_mkvmerge_available()
    ffmpeg_available = is_ffmpeg_available()
    if not (mkvmerge_available or ffmpeg_available) or (
            (not mkvmerge_available and copy) or (not ffmpeg_available and not copy)):
        split_tool = 'ffmpeg/mkvmerge'
        if (not mkvmerge_available and copy):
            split_tool = 'mkvmerge'
        elif (not ffmpeg_available and not copy):
            split_tool = 'ffmpeg'
        error_strs = [
            "{EXTERN_TOOL} is required for split-video{EXTRA_ARGS}.".format(
                EXTERN_TOOL=split_tool, EXTRA_ARGS=' -c/--copy' if copy else ''),
            "Install the above tool%s to enable video splitting support." % (
                's' if split_tool.find('/') > 0 else '')]
        if mkvmerge_available:
            error_strs += [
                'You can also specify `split-video -c/--copy` to use mkvmerge for splitting.']
        error_str = '\n'.join(error_strs)
        logging.debug(error_str)
        ctx.obj.options_processed = False
        raise click.BadParameter(error_str, param_hint='split-video')
예제 #2
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 = self.get_output_file_path(
                scene_list_filename, self.scene_list_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)

        # 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 = self.get_output_file_path(html_filename,
                                                  self.image_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 = self.get_output_file_path(
                self.split_name_format, output_dir=self.split_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.'
                )
예제 #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 = self.get_output_file_path(
                scene_list_filename, self.scene_list_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)

        # 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 = self.get_output_file_path(
                html_filename, self.image_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 = self.get_output_file_path(
                self.split_name_format, output_dir=self.split_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.')