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

VideoFfmpegSplitKeyframe

Video Problem Overview


We need to split a large live WMV video feed in small chunks all of the same size. We made a script that works fine doing this, except for one thing: the video chunks don't start with a key frame, so when playing most video chunks they don't display any image until a key frame from the original video is eventually reached.

Isn't there a way to tell ffmpeg to make the output video to start with a key frame?

Here is how our command lines look right now:

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

and so on...

Video Solutions


Solution 1 - Video

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.

Solution 2 - Video

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.

Solution 3 - Video

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.

Solution 4 - Video

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)

Solution 5 - Video

Use ffprobe -show_frames -pretty <stream> to identify the key frames.

Solution 6 - Video

If you are willing to do some scripting and want I frames at a particular interval the one way to do it is

  1. Run ffprobe and collect the locations of the I frames from the output

    ffprobe -show_streams

  2. Run a series of -ss -t commands using the same script to get the chunks you desire.

You can then have your script decide minimum number of frames [say there are two I pictures within 10 frames of each other, you really don't want to be chunking it there].

The other way to do it is to use gstreamer's multisfilesink and set the mode to key frame [however that will chunk at every key frame so that may not be ideal]

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsboisseView Question on Stackoverflow
Solution 1 - VideoTim BullView Answer on Stackoverflow
Solution 2 - VideoSopalajo de ArrierezView Answer on Stackoverflow
Solution 3 - VideosboisseView Answer on Stackoverflow
Solution 4 - VideoSteekView Answer on Stackoverflow
Solution 5 - Videod33pikaView Answer on Stackoverflow
Solution 6 - Videoav501View Answer on Stackoverflow