How to split a video using FFMPEG so that each chunk starts with a key frame?

Using a newer build of ffmpeg, can achieve this by using ffprobe and the ffmpeg segment muxer.

  1. Use ffprobe and awk to identify the keyframes as close as possible to your desired chunk length.
ffprobe -show_frames -select_streams v:0 \
        -print_format csv [SOURCE_VIDEO] 2>&1 |
grep -n frame,video,1 |
awk 'BEGIN { FS="," } { print $1 " " $5 }' |
sed 's/:frame//g' |
awk 'BEGIN { previous=0; frameIdx=0; size=0; } 
{
  split($2,time,".");
  current=time[1];
  if (current-previous >= [DURATION_IN_SECONDS]){
    a[frameIdx]=$1; frameIdx++; size++; previous=current;
  }
}
END {
  str=a[0];
  for(i=1;i<size;i++) { str = str "," a[i]; } print str;
}'

Where

  • [SOURCE_VIDEO] = path to video you want to segment
  • [DURATION_IN_SECONDS] = desired segment length in seconds

The output is comma-delimited string of keyframes.

  1. Use the keyframes output above as input to ffmpeg.
ffmpeg -i [SOURCE_VIDEO] -codec copy -map 0 -f segment \
       -segment_frames [OUTPUT_OF_STEP_1] [SEGMENT_PREFIX] \
       _%03d.[SOURCE_VIDEO_EXTENSION]

Where

  • [SOURCE_VIDEO] = path to video you want to segment
  • [OUTPUT_OF_STEP_1] = comma-delimited string of keyframes
  • [SEGMENT_PREFIX] = name of segment output
  • [SOURCE_VIDEO_EXTENSION] = extension of source video (e.g., mp4, mkv)

The latest builds of FFMPEG include a new option "segment" which does exactly what I think you need.

ffmpeg -i INPUT.mp4 -acodec copy -f segment -vcodec copy -reset_timestamps 1 -map 0 OUTPUT%d.mp4

This produces a series of numbered output files which are split into segments based on Key Frames. In my own testing, it's worked well, although I haven't used it on anything longer than a few minutes and only in MP4 format.


As stated on the official FFMPEG Docs, it has worked better for me to specify -ss timestart before -i input_file.ext, because it sets (or so I understand) the beginning of the generated video to the nearest keyframe found before your specified timestamp.

Change your example to:

ffmpeg.exe -ss 00:00:00 -i "C:\test.wmv" -t 00:00:05 -acodec copy -vcodec copy -async 1 -y  "0000.wmv"

I have tested this method working on .flv and .mp4 files.


Here is the solution that I could get to work:

As suggested by av501 and d33pika, I used ffprobe to find where the key frames are. Because ffprobe is very verbose and can take several seconds or even minutes to output all key frames and there is no way to scope the range of frames we want from a lengthy video, I proceed into 5 steps:

  1. Export a video chunk from the original file, around the double of the desired chunk size.

    ffmpeg -i source.wmv -ss 00:00:00 -t 00:00:06 -acodec copy -vcodec copy -async 1 -y  0001.wmv
    
  2. Use ffprobe to find where the keyframes are. Choose closest keyframe after desired chunk size.

    ffprobe -show_frames -select_streams v -print_format json=c=1 0001.wmv
    
  3. From the output of ffprobe get the pkt_dts_time of the frame just before that key frame.

  4. ffmpeg on the exported chunk of step 1, specifying the same input and output file, and specifying -ss 00:00:00 and -t [value found in step 3].

    ffmpeg -i 0001.wmv -ss 00:00:00 -t 00:00:03.1350000 -acodec copy -vcodec copy -async 1 -y 0001.wmv
    
  5. Restart at step 1, using -ss [cumulated sum of values found in step 3 over iterations].

Proceeding this way, I was able to have an efficient and robust way to split the video at key frames.