M: In ffmpeg, is it possible to partially reencode? For example, if you want to cut between two key frames.

G: Yes, it's possible to partially reencode with FFmpeg by using smart rendering or stream copying where possible. If you're cutting between two keyframes, you can use -c copy to avoid reencoding entirely, but if you need to cut at a non-keyframe, only the affected GOP (Group of Pictures) will need reencoding.

Two Approaches:

  1. Cutting Without Reencoding (-c copy)
    If both cut points are keyframes, use:

ffmpeg -i input.mp4 -ss start_time -to end_time -c copy output.mp4

This method is fast and lossless but only works if your cut points are at keyframes.

  • Partial Reencoding (Smart Reencoding with -ss and -to)
    If the cut includes non-keyframes, FFmpeg will reencode only the affected sections:

    ffmpeg -i input.mp4 -ss start_time -to end_time -c:v libx264 -preset fast -crf 18 -c:a copy output.mp4

    Here, video is reencoded while audio is copied to avoid unnecessary processing.

  • For finer control, you could use FFmpeg's segmenting and concat features to reencode only necessary portions and stitch them back together.

    M: How can you tell using ffmpeg if a particular frame is a key frame, for the purpose of logic tests

    G: You can determine if a frame is a keyframe in FFmpeg using the -select_streams v -show_frames option with ffprobe. Here’s how:

    Command:

    ffprobe -i input.mp4 -select_streams v -show_frames -print_format json | jq '.frames[] | select(.key_frame == 1)'

    Without jq:

    If you don’t have jq, you can use:

    ffprobe -i input.mp4 -select_streams v -show_frames | grep 'key_frame=1'

    Checking Keyframe Time:

    To get a list of keyframe timestamps:

    ffprobe -i input.mp4 -select_streams v -show_frames -print_format csv | grep ",1," | cut -d',' -f6

    This extracts the pkt_pts_time (timestamp) of each keyframe.

    You can use this information in a script to decide if a given timestamp is a keyframe before making cuts.

    M: Provide a basic bash script. Given a video file X.mp4 and a timestamp T: if T is a key frame in X, print T. Otherwise, identify and print the timestamp of the next key frame after T.

    G: Here’s a basic Bash script to check if a given timestamp T is a keyframe in X.mp4 and, if not, find the next keyframe after T.

    Script (find_next_keyframe.sh)

    #!/bin/bash
    
    # Check arguments
    if [[ $# -ne 2 ]]; then
        echo "Usage: $0 <video_file> <timestamp>"
        exit 1
    fi
    
    VIDEO_FILE="$1"
    TIMESTAMP="$2"
    
    # Extract keyframe timestamps
    KEYFRAMES=$(ffprobe -i "$VIDEO_FILE" -select_streams v -show_frames -print_format csv 2>/dev/null | awk -F',' '$5 == 1 {print $6}')
    
    NEXT_KEYFRAME=""
    
    # Find the next keyframe after the given timestamp
    for KF in $KEYFRAMES; do
        if (( $(echo "$KF >= $TIMESTAMP" | bc -l) )); then
            NEXT_KEYFRAME="$KF"
            break
        fi
    done
    
    # Output result
    if [[ "$NEXT_KEYFRAME" == "$TIMESTAMP" ]]; then
        echo "$TIMESTAMP"
    else
        echo "$NEXT_KEYFRAME"
    fi

    Usage:

    chmod +x find_next_keyframe.sh
    ./find_next_keyframe.sh X.mp4 12.34

    Explanation:

    1. Extracts all keyframe timestamps using ffprobe.
    2. Iterates through them to check if T is a keyframe.
    3. If T is a keyframe, it prints T.
    4. Otherwise, it prints the next keyframe timestamp.

    This should work efficiently for most videos.

    M: Is that script compatible with the 00:00:00.000 time format

    G: No, the script as written expects timestamps in decimal seconds (e.g., 12.34). If you want to support HH:MM:SS.sss format, you need to convert it to decimal seconds before comparison. Here's a revised version:


    Updated Script (find_next_keyframe.sh)

    #!/bin/bash
    
    # Check arguments
    if [[ $# -ne 2 ]]; then
        echo "Usage: $0 <video_file> <timestamp (HH:MM:SS.sss or seconds)>"
        exit 1
    fi
    
    VIDEO_FILE="$1"
    TIMESTAMP_RAW="$2"
    
    # Function to convert HH:MM:SS.sss to seconds
    convert_to_seconds() {
        local time="$1"
        if [[ "$time" =~ ^([0-9]+):([0-9]+):([0-9]+(\.[0-9]+)?)$ ]]; then
            local h="${BASH_REMATCH[1]}"
            local m="${BASH_REMATCH[2]}"
            local s="${BASH_REMATCH[3]}"
            echo "$(bc -l <<< "$h * 3600 + $m * 60 + $s")"
        else
            echo "$time"  # Assume it's already in seconds
        fi
    }
    
    # Convert input timestamp to seconds
    TIMESTAMP=$(convert_to_seconds "$TIMESTAMP_RAW")
    
    # Extract keyframe timestamps in decimal seconds
    KEYFRAMES=$(ffprobe -i "$VIDEO_FILE" -select_streams v -show_frames -print_format csv 2>/dev/null | awk -F',' '$5 == 1 {print $6}')
    
    NEXT_KEYFRAME=""
    
    # Find the next keyframe after the given timestamp
    for KF in $KEYFRAMES; do
        if (( $(echo "$KF >= $TIMESTAMP" | bc -l) )); then
            NEXT_KEYFRAME="$KF"
            break
        fi
    done
    
    # Output result
    if [[ -z "$NEXT_KEYFRAME" ]]; then
        echo "No keyframe found after $TIMESTAMP_RAW"
    else
        echo "$NEXT_KEYFRAME"
    fi

    Changes & Improvements

    Usage Examples

    ./find_next_keyframe.sh X.mp4 00:01:12.500  # HH:MM:SS.sss format
    ./find_next_keyframe.sh X.mp4 72.5           # Decimal seconds format

    Let me know if you need further refinements.