36

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?

Oliver Salzburg
  • 89,072
  • 65
  • 269
  • 311
curmil
  • 1,135

4 Answers4

40

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.

slhck
  • 235,242
4

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...

sdaau
  • 6,008
0

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?

Masutin
  • 41
0

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
Gilles Quénot
  • 4,475
  • 1
  • 30
  • 28