I'm having trouble finding a cli application that can take a video file (avi, mkv, and mp4 preferably) and cut out very short clips (2-6 seconds) with precision time accuracy. I've tried ffmpeg, mencoder, avidemux, and mp4box but they all cut on keyframes which creates 6+ second clips. Is there a tool that will re-encode the input file and cut the accurate time or cut inaccurately, re-encode, and then cut accurately?
4 Answers
Cutting video with ffmpeg
You can accurately cut videos with FFmpeg. Since version 2.5 it's very easy.
This would for example cut 10 seconds, starting from 0 minutes, 3 seconds and 123 milliseconds.
ffmpeg -ss 00:00:03.123 -i input.mp4 -t 10 -c:v libx264 -c:a copy out.mp4
The position and the time may be either in seconds or in hh:mm:ss[.xxx] form.
Note that in these examples, video will be re-encoded using the x264 encoder; audio is copied over.
You can also use -to instead of -t to specify the end point instead of the duration. In this case, however, -to is equivalent to -t, since by putting the -ss in front of -i, ffmpeg will first seek to that point and then start outputting.
If the output does not appear to be cut correctly, adding -fflags +genpts to the command may help.
See also the Seeking wiki entry.
- 235,242
The only Linux command-line tool I've found so-far, that can cut at exact frame (or, with frame accuracy), is melt (sudo apt-get install melt).
Say you have an inputvid.mp4 - first check its encoding settings with say ffmpeg (here, I just say I want to encode it again to -f mp4, but as the file /dev/null so the output is discarded; I redirect stderr so I can grep through it - note in the middle, the command prompts, and you should answer y with ENTER, so the process proceeds and dumps the useful info; this is with ffmpeg 3.3.3 on Ubuntu 14):
ffmpeg -i inputvid.mp4 -f mp4 /dev/null 2>&1 | grep 'Stream\|encoder'
Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(tv, bt709), 640x360 [SAR 1:1 DAR 16:9], 389 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 95 kb/s (default)
y
File '/dev/null' already exists. Overwrite ? [y/N] Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
Stream #0:1 -> #0:1 (aac (native) -> aac (native))
encoder : Lavf57.71.100
Stream #0:0(und): Video: h264 (libx264) ([33][0][0][0] / 0x0021), yuv420p(progressive), 640x360 [SAR 1:1 DAR 16:9], q=-1--1, 23.98 fps, 24k tbn, 23.98 tbc (default)
encoder : Lavc57.89.100 libx264
Stream #0:1(und): Audio: aac (LC) ([64][0][0][0] / 0x0040), 44100 Hz, stereo, fltp, 128 kb/s (default)
encoder : Lavc57.89.100 aac
Ok, so we can see ffmpeg chooses libx264 and aac encoders for this video; then we can enter this in for melt:
melt inputvid.mp4 in=7235 out=7349 -consumer avformat:cut.mp4 acodec=aac vcodec=libx264
.... and melt will cut with the piece between frames 7235 and 7349 into a new file, cut.mp4. Then to check if cut.mp4 loops correctly, use melt again to play it back twice - and play it to an SDL window:
melt cut.mp4 cut.mp4 -consumer sdl
... and here is what ffmpeg sees for this file:
ffmpeg -i cut.mp4 -f mp4 /dev/null 2>&1 | grep 'Stream\|encoder' encoder : Lavf54.20.4
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 526 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 182 kb/s (default)
y
File '/dev/null' already exists. Overwrite ? [y/N] Stream mapping:
Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
Stream #0:1 -> #0:1 (aac (native) -> aac (native))
encoder : Lavf57.71.100
Stream #0:0(und): Video: h264 (libx264) ([33][0][0][0] / 0x0021), yuv420p, 640x360 [SAR 1:1 DAR 16:9], q=-1--1, 23.98 fps, 24k tbn, 23.98 tbc (default)
encoder : Lavc57.89.100 libx264
Stream #0:1(und): Audio: aac (LC) ([64][0][0][0] / 0x0040), 48000 Hz, stereo, fltp, 128 kb/s (default)
encoder : Lavc57.89.100 aac
The video encoding settings for cut.mp4 seem to be identical to inputvid.mp4 except video bitrate changed from 389 kb/s to 526 kb/s, and also the audio encoding settings are nearly the same, except the sampling rate changed from 44100 to 48000 Hz; though that can be regulated with:
melt inputvid.mp4 in=7235 out=7349 -consumer avformat:cut.mp4 acodec=aac ar=44100 ab=95k vcodec=libx264 vb=389k
... however, even with this, the final video bitrate for me ends up 337 kb/s. Still, the cuts loop fine (and that includes audio) when played in a loop, so I guess this is indeed frame-accurate...
- 6,008
In an answer to a similar question unreliable results with -ss in earlier versions are mentioned. But in new builds it appears the same. To recap, if you use ss/to/t before -i, it cuts at i-frames, after -i, it cuts at specified times and uses the next i-frame till this frame is reached, i.e. you get a still picture to that time. At the time for -to it may cut as requested, if it's no coincidence. I thought you could get an accurate cut by encoding the part before the i-frame and copying the part after, but apparently you can't join the resulting files. Can we safely conclude that an accurate cut off i-frames is impossible without encoding the whole video?
- 41
Like Baodad said in the comments (I post because it's not easy to find if you read quickly), the better approach is to detect the audio/video encoders automatically by ffmpeg, so :
ffmpeg -ss 00:05:17.18 -i in.mp4 -t 00:06:29.10 -acodec copy -vcodec copy out.mp4
- start @ 00:05:17.18
- input = in.mp4
- stop @ 00:06:29.10
- output = out.mp4
- 4,475
- 1
- 30
- 28