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')
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.' )
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.')