Breakthrough / PySceneDetect

:movie_camera: Python and OpenCV-based scene cut/transition detection program & library.
https://www.scenedetect.com/
BSD 3-Clause "New" or "Revised" License
2.97k stars 374 forks source link

split_video_ffmpeg lib function update #363

Closed Deepchavda007 closed 7 months ago

Deepchavda007 commented 7 months ago

Description: split_video_ffmpeg inj this function we can't save specific output dir.

Example:

from scenedetect import detect, ContentDetector, split_video_ffmpeg
scene_list = detect('my_video.mp4', ContentDetector())
split_video_ffmpeg('my_video.mp4', scene_list)

Now we can save specific output dir.

from scenedetect import detect, ContentDetector, split_video_ffmpeg
scene_list = detect('my_video.mp4', ContentDetector())
split_video_ffmpeg(video_path, scene_list, output_dir=video_clips_dir)

video_splitter.py in this file update this function:

def split_video_ffmpeg(
    input_video_path: str,
    scene_list: Iterable[TimecodePair],
    output_file_template: str = '$VIDEO_NAME-Scene-$SCENE_NUMBER.mp4',
    video_name: Optional[str] = None,
    arg_override: str = DEFAULT_FFMPEG_ARGS,
    show_progress: bool = False,
    show_output: bool = False,
    suppress_output=None,
    hide_progress=None,
    output_dir: Optional[str] = None
):
    """ Calls the ffmpeg command on the input video, generating a new video for
    each scene based on the start/end timecodes.

    Arguments:
        input_video_path: Path to the video to be split.
        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.mp4`
        video_name (str): Name of the video to be substituted in output_file_template. If not
            passed will be calculated from input_video_path automatically.
        arg_override (str): Allows overriding the arguments passed to ffmpeg for encoding.
        show_progress (bool): If True, will show progress bar provided by tqdm (if installed).
        show_output (bool): If True, will show output from ffmpeg for first split.
        suppress_output: [DEPRECATED] DO NOT USE. For backwards compatibility only.
        hide_progress: [DEPRECATED] DO NOT USE. For backwards compatibility only.

    Returns:
        Return code of invoking ffmpeg (0 on success). If scene_list is empty, will
        still return 0, but no commands will be invoked.
    """
    # Handle backwards compatibility with v0.5 API.
    if isinstance(input_video_path, list):
        logger.error('Using a list of paths is deprecated. Pass a single path instead.')
        if len(input_video_path) > 1:
            raise ValueError('Concatenating multiple input videos is not supported.')
        input_video_path = input_video_path[0]
    if suppress_output is not None:
        logger.error('suppress_output is deprecated, use show_output instead.')
        show_output = not suppress_output
    if hide_progress is not None:
        logger.error('hide_progress is deprecated, use show_progress instead.')
        show_progress = not hide_progress

    if not scene_list:
        return 0

    logger.info('Splitting input video using ffmpeg, output path template:\n  %s',
                output_file_template)

    if video_name is None:
        video_name = get_file_name(input_video_path, include_extension=False)

    # Ensure the output directory exists
    if output_dir is not None and not os.path.exists(output_dir):
        os.makedirs(output_dir)

    arg_override = arg_override.replace('\\"', '"')

    ret_val = 0
    arg_override = arg_override.split(' ')
    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 show_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)
            # Format output filename with template variable
            output_filename  = Template(output_file_template).safe_substitute(
                VIDEO_NAME=video_name,
                SCENE_NUMBER=scene_num_format % (i + 1),
                START_TIME=str(start_time.get_timecode().replace(":", ";")),
                END_TIME=str(end_time.get_timecode().replace(":", ";")),
                START_FRAME=str(start_time.get_frames()),
                END_FRAME=str(end_time.get_frames()))

            if output_dir is not None:
                output_filepath = os.path.join(output_dir, output_filename)
            else:
                output_filepath = output_filename

            # Gracefully handle case where FFMPEG_PATH might be unset.
            call_list = [FFMPEG_PATH if FFMPEG_PATH is not None else 'ffmpeg']
            if not show_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_path, '-t',
                str(duration.get_seconds())
            ]
            call_list += arg_override
            call_list += ['-sn']
            # call_list += [output_file_template_iter]
            call_list += [output_filepath]
            ret_val = invoke_command(call_list)
            if show_output and i == 0 and len(scene_list) > 1:
                logger.info(
                    'Output from ffmpeg for Scene 1 shown above, splitting remaining scenes...')
            if ret_val != 0:
                # TODO(v0.6.2): Capture stdout/stderr and display it on any failed calls.
                logger.error('Error splitting video (ffmpeg returned %d).', ret_val)
                break
            if progress_bar:
                progress_bar.update(duration.get_frames())

        if progress_bar:
            progress_bar.close()
        if show_output:
            logger.info('Average processing speed %.2f frames/sec.',
                        float(total_frames) / (time.time() - processing_start_time))

    except CommandTooLong:
        logger.error(COMMAND_TOO_LONG_STRING)
    except OSError:
        logger.error('ffmpeg could not be found on the system.'
                     ' Please install ffmpeg to enable video output support.')
    return ret_val
Breakthrough commented 7 months ago

Originally I had closed this issue as part of #298, but this has come up enough now that we should probably add support for this. I'll close this issue as a duplicate and re-open #298 for the following release. Thanks!