def test_long_command(): """ [Windows Only] Ensures that a command string too large to be handled is translated to the correct exception for error handling. """ if platform.system() == 'Windows': with pytest.raises(CommandTooLong): invoke_command('x' * 2**15)
def test_invoke_command(): """ Ensures the function exists and is callable without throwing an exception. """ if platform.system() == 'Windows': invoke_command(['cmd']) else: invoke_command(['echo'])
def split_video_mkvmerge(input_video_paths, scene_list, output_file_template, video_name, suppress_output=False): # type: (List[str], List[FrameTimecode, FrameTimecode], Optional[str], # Optional[bool]) -> None """ Calls the mkvmerge command on the input video(s), splitting it at the passed timecodes, where each scene is written in sequence from 001. Arguments: input_video_paths (List[str]): List of strings to the input video path(s). Is a list to allow for concatenation of multiple input videos together. scene_list (List[Tuple[FrameTimecode, FrameTimecode]]): List of scenes (pairs of FrameTimecodes) denoting the start/end frames of each scene. output_file_template (str): Template to use for output files. Note that the scene number is automatically appended to the prefix by mkvmerge. Can use $VIDEO_NAME as a parameter in the template. video_name (str): Name of the video to be substituted in output_file_template. suppress_output (bool): If True, adds the --quiet flag when invoking `mkvmerge`. """ if not input_video_paths or not scene_list: return logging.info('Splitting input video%s using mkvmerge, output path template:\n %s', 's' if len(input_video_paths) > 1 else '', output_file_template) ret_val = None # mkvmerge automatically appends '-$SCENE_NUMBER'. output_file_name = output_file_template.replace('-${SCENE_NUMBER}', '') output_file_name = output_file_template.replace('-$SCENE_NUMBER', '') output_file_template = Template(output_file_name) output_file_name = output_file_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER='') try: call_list = ['mkvmerge'] if suppress_output: call_list.append('--quiet') call_list += [ '-o', output_file_name, '--split', #'timecodes:%s' % ','.join( # [start_time.get_timecode() for start_time, _ in scene_list[1:]]), 'parts:%s' % ','.join( ['%s-%s' % (start_time.get_timecode(), end_time.get_timecode()) for start_time, end_time in scene_list]), ' +'.join(input_video_paths)] total_frames = scene_list[-1][1].get_frames() - scene_list[0][0].get_frames() processing_start_time = time.time() ret_val = invoke_command(call_list) if not suppress_output: print('') logging.info('Average processing speed %.2f frames/sec.', float(total_frames) / (time.time() - processing_start_time)) except CommandTooLong: logging.error(COMMAND_TOO_LONG_STRING) except OSError: logging.error('mkvmerge could not be found on the system.' ' Please install mkvmerge to enable video output support.') if ret_val is not None and ret_val != 0: logging.error('Error splitting video (mkvmerge returned %d).', ret_val)
def split_video(input_video_paths, scene_list, output_file_template, video_name, suppress_output=False): if not input_video_paths or not scene_list: return logging.info( 'Splitting input video%s using mkvmerge, output path template:\n %s', 's' if len(input_video_paths) > 1 else '', output_file_template) ret_val = None # mkvmerge automatically appends '-$SCENE_NUMBER'. output_file_name = output_file_template.replace('-${SCENE_NUMBER}', '') output_file_name = output_file_template.replace('-$SCENE_NUMBER', '') output_file_template = Template(output_file_name) output_file_name = output_file_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER='') # Actually make the system call to MKVMerge try: call_list = ['mkvmerge'] call_list += [ '-o', output_file_name, '--split', 'parts:%s' % ','.join([ '%s-%s' % (start_time.get_timecode(), end_time.get_timecode()) for start_time, end_time in scene_list ]), "data/rawVideos/{}".format(input_video_paths) ] total_frames = scene_list[-1][1].get_frames( ) - scene_list[0][0].get_frames() processing_start_time = time.time() ret_val = invoke_command(call_list) print('') prettyPrint( bcolors.OKBLUE, ('Average processing speed %.2f frames/sec.', float(total_frames) / (time.time() - processing_start_time))) # Basically just some error handling except CommandTooLong: prettyPrint(bcolors.WARNING, "COMMAND_TOO_LONG_STRING: {}".format(call_list)) except OSError: prettyPrint( bcolors.WARNING, 'mkvmerge could not be found on the system.' ' Please install mkvmerge to enable video output support.') if ret_val is not None and ret_val != 0: prettyPrint(bcolors.WARNING, ('Error splitting video (mkvmerge returned %d).', ret_val))
def split_video_ffmpeg(input_video_paths, scene_list, output_file_template, video_name, arg_override='-c:v libx264 -preset fast -crf 21 -c:a aac', hide_progress=False, suppress_output=False): # type: (List[str], List[Tuple[FrameTimecode, FrameTimecode]], Optional[str], # Optional[str], Optional[bool], Optional[bool]) -> None """ Calls the ffmpeg command on the input video(s), generating a new video for each scene based on the start/end timecodes. Arguments: input_video_paths (List[str]): List of strings to the input video path(s). Is a list to allow for concatenation of multiple input videos together. scene_list (List[Tuple[FrameTimecode, FrameTimecode]]): List of scenes (pairs of FrameTimecodes) denoting the start/end frames of each scene. output_file_template (str): Template to use for generating the output filenames. Can use $VIDEO_NAME and $SCENE_NUMBER in this format, for example: `$VIDEO_NAME - Scene $SCENE_NUMBER` video_name (str): Name of the video to be substituted in output_file_template. arg_override (str): Allows overriding the arguments passed to ffmpeg for encoding. hide_progress (bool): If True, will hide progress bar provided by tqdm (if installed). suppress_output (bool): If True, will set verbosity to quiet for the first scene. """ if not input_video_paths or not scene_list: return logging.info( 'Splitting input video%s using ffmpeg, output path template:\n %s', 's' if len(input_video_paths) > 1 else '', output_file_template) if len(input_video_paths) > 1: # TODO: Add support for splitting multiple/appended input videos. # https://trac.ffmpeg.org/wiki/Concatenate#samecodec # Requires generating a temporary file list for ffmpeg. logging.error( 'Sorry, splitting multiple appended/concatenated input videos with' ' ffmpeg is not supported yet. This feature will be added to a future' ' version of PySceneDetect. In the meantime, you can try using the' ' -c / --copy option with the split-video to use mkvmerge, which' ' generates less accurate output, but supports multiple input videos.') raise NotImplementedError() arg_override = arg_override.replace('\\"', '"') ret_val = None arg_override = arg_override.split(' ') filename_template = Template(output_file_template) scene_num_format = '%0' scene_num_format += str(max(3, math.floor(math.log(len(scene_list), 10)) + 1)) + 'd' try: progress_bar = None total_frames = scene_list[-1][1].get_frames() - scene_list[0][0].get_frames() if tqdm and not hide_progress: progress_bar = tqdm( total=total_frames, unit='frame', miniters=1, dynamic_ncols=True) processing_start_time = time.time() for i, (start_time, end_time) in enumerate(scene_list): duration = (end_time - start_time) call_list = ['ffmpeg'] if suppress_output: call_list += ['-v', 'quiet'] elif i > 0: # Only show ffmpeg output for the first call, which will display any # errors if it fails, and then break the loop. We only show error messages # for the remaining calls. call_list += ['-v', 'error'] call_list += [ '-nostdin', '-y', '-ss', str(start_time.get_seconds()), '-i', input_video_paths[0], '-t', str(duration.get_seconds()) ] call_list += arg_override call_list += [ '-sn', filename_template.safe_substitute( VIDEO_NAME=video_name, SCENE_NUMBER=scene_num_format % (i + 1)) ] ret_val = invoke_command(call_list) if not suppress_output and i == 0 and len(scene_list) > 1: logging.info( 'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...') if ret_val != 0: break if progress_bar: progress_bar.update(duration.get_frames()) if progress_bar: print('') logging.info('Average processing speed %.2f frames/sec.', float(total_frames) / (time.time() - processing_start_time)) except CommandTooLong: logging.error(COMMAND_TOO_LONG_STRING) except OSError: logging.error('ffmpeg could not be found on the system.' ' Please install ffmpeg to enable video output support.') if ret_val is not None and ret_val != 0: logging.error('Error splitting video (ffmpeg returned %d).', ret_val)
def test_invoke_command(): """ Ensures the function exists and is callable without throwing an exception. """ invoke_command(['echo'])