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
.
- 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.
- 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:
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
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
From the output of ffprobe get the
pkt_dts_time
of the frame just before that key frame.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
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.