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
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.
Use ffprobe -show_frames -pretty <stream>
to identify the key frames.
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.
If you are willing to do some scripting and want I frames at a particular interval the one way to do it is
Run ffprobe and collect the locations of the I frames from the output
ffprobe -show_streams
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]
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.
Using a newer build of ffmpeg
, can achieve this by using ffprobe
and the ffmpeg
segment muxer
.
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
The output is comma-delimited string of keyframes.
ffmpeg
.ffmpeg -i [SOURCE_VIDEO] -codec copy -map 0 -f segment \
-segment_frames [OUTPUT_OF_STEP_1] [SEGMENT_PREFIX] \
_%03d.[SOURCE_VIDEO_EXTENSION]
Where