예제 #1
0
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)
예제 #2
0
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'])
예제 #3
0
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)
예제 #4
0
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))
예제 #5
0
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)
예제 #6
0
def test_invoke_command():
    """ Ensures the function exists and is callable without throwing
    an exception. """
    invoke_command(['echo'])