How to extract time-accurate video segments with ffmpeg?
I think the problem the question and other answers are having is that they're using -ss
as an option on the output file, not on the input file. Most ffmpeg options aren't global, but instead only apply to the file they precede. It's often not obvious where an option needs to go, so sometimes trial and error is required.
Used correctly, before the input file they're meant to apply to, -ss
and -t
worked fine for me. When including audio in the output, I had to use -shortest
as an option for the output file, or I'd get 2 mins of audio with 2 secs of video.
ffmpeg version N-67413-g2a88c74 (basically git source from Dec 14 2014)
Here's a command line I used to make a clip recently. (actually tweaked to be a better example, since I left in audio for this, and didn't slo-mo it.)
ffmpeg -ss 120.2 -t 0.75 -i ../mcdeint.60p.lossless264.slow.mkv -c:a libopus -shortest -aspect 16:9 -preset veryslow -x264-params nr=250:ref=6 -crf 22 -movflags +faststart clip2.mkv
With -c:a copy
(source has AC3 audio), playback starts up wonky with mplayer. It probably grabs audio from the beginning of the audio frame containing the start, and then has to use a a/v offset in the container. On startup, it takes fraction of a second for the audio to get ahead of the video by that offset, and until then the video is playing at very low FPS. So I xcoded the audio. Neither opus nor pcm_s16le can go in mp4, so I used an mkv container for this example.
The source is a lossless x264 encode (-qp 0
) from the output of a very slow yadif=3:1,mcdeint=3:1:10 (on some BFF interlaced video from an NTSC DVD, probably from a DV camera). It's NOT all I
frames, it's P
frames with a normal keyframe interval.
Adjusting -ss by 0.2 secs did exactly what I hoped, so ffmpeg must handle decoding up to the required point. Wasn't just a coincidence of an I frame where I wanted one. Maybe -accurate_seek
is the default? I also get the same result (byte-for-byte identical gif output) as when I use a ffvhuff lossless source as input. (but it runs faster, since it doesn't have to decode up to the requested point.)
Another possibly relevant option is -seek2any
, but it says "seek to non-keyframe on demuxer level", which sounds like it would let you seek in ways that would produce garbled output. (i.e. start decoding without actually generating the references that the current frame requires, just use all-gray?)
I haven't tried using -c:v copy
, since I'm cutting out a really short clip to loop, so I know there won't be I
frames where I need them.
This is the command line I actually used, to make a short slo-mo clip with no sound.
ffmpeg -ss 120.2 -t 0.75 -i ../vid.yadif3.1,mcdeint3.1.10.ffvhuff.mkv -an -shortest -aspect 16:9 -c:v libx264 -preset veryslow -x264-params nr=250:ref=6 -crf 22 -filter:v "setpts=3.0*PTS" -movflags +faststart -r 20 clip.mp4
Note that the -r 20
was important, because unlike mkv, ffmpeg's MP4 output is constant-frame-rate-only (edit: because -vsync vfr
isn't the default with the mp4 muxer). Without telling it different, it would set the output FPS = input FPS, and duplicate frames when needed to make that happen. x264 and animated gif (with transparency) can both encode duplicate frames very efficiently, but it's still silly.
Before cooking this up as an example, I had done it in 2 steps, one outputting to mkv, then ffmpeg -i clip.mkv -c:v copy -movflags +faststart -r 20 clip.mp4
to remux. BTW, it's possible to change the fps of a video when remuxing, without xcoding, just not with ffmpeg. https://superuser.com/questions/740196/reducing-video-size-with-avconv-why-does-the-size-increase. But anyway, ffmpeg only sent 45 frames to libx264 when making the mkv, even though it thought it was making a 2.2 second video at 60fps. Don't use ffmpeg with mp4 to work with variable FPS stuff.
edit: turns out ffmpeg defaults to -vsync vfr
for mkv output, but not for mp4. With -vsync vfr
, ffmpeg can write VFR into mp4 output just fine.
And again for gif output, in case I decide not to put it up with HTML5 video (<video controls autoplay loop> <source src="clip.mp4" type="video/mp4"> </video>
)
ffmpeg -ss 120.2 -t 0.75 -i ../vid.yadif3.1,mcdeint3.1.10.ffvhuff.mkv -an -shortest -aspect 16:9 -filter:v "setpts=3.0*PTS,scale=854x480" -r 20 clip.gif
I had to use scale=
because the gif container doesn't store an aspect ratio, so it can't get auto-scaled on playback. (my 720x480 pixel 16:9 video gets scaled to 854x480 on playback. Actually should be 853.333, but that gets rounded, and then ffmpeg stores 853x480 in the mkv container, hence using -aspect 16:9 all the time, so my mp4 will store the proper aspect ratio [SAR 32:27 DAR 16:9]
, instead of [SAR 186:157 DAR 279:157]
)
Ok, first of all assuming you know the start and stop duration; we will add key-frames at that duration.
ffmpeg -i a.mp4 -force_key_frames 00:00:09,00:00:12 out.mp4
Most of the time you can directly cut the video with perfection but in your case it does not help you; so we have taken care of it by above command. Here be cautious to not add too many key frames as it can be a problem while encoding as per Ffmpeg Docs.
Now you can again try to cut the video from specific time.
ffmpeg -ss 00:00:09 -i out.mp4 -t 00:00:03 -vcodec copy -acodec copy -y final.mp4
This will solve the problem as we have manually added the keyframes at start and end points of the cut . It worked for me.
Cheers.:)
Poking a sleeping bear in the woods ð§♂️. I didn't see any mention of the -to
flag mentioned in this thread, and I found it more useful too use than the -t
flag. An example command that works well for me,
ffmpeg -y -i [INPUT.file] -ss 00:42:42 -to 00:84:84 -codec copy [OUTPUT.file]