Cropping and cutting have the same meaning but movie processing software seems to distinguish between the two:
The following command:
ffmpeg -i example.mov -filter:v "crop=w:h:x:y" result.mov
trims a movie called example.mov
and outputs the result in result.mov
. The trimming is performed by selecting a rectangle, w
pixels wide by h
pixel high, starting from the screen coordinates x
and y
from the top-left corner of the screen.
The following example:
ffmpeg -i example.mov -ss 00:00:02 -t 00:00:28 result.mov
cuts a slice starting from 2s
into the movie and cuts out 28s
starting from that point.
The following command goes through the directories starting from where the command was issued and converts FLAC files to MP3 files. The one-liner requires ffmpeg
and lame
to be installed.
Input file:
./TIPWCD001 - Various Artists - Halluci-Nations (1999)/06_Process_-_Blue_Moonies.flac
Output file:
./TIPWCD001 - Various Artists - Halluci-Nations (1999)/06_Process_-_Blue_Moonies.mp3
find . -name "*.flac" -exec sh -c 'ffmpeg -y -i "{}" -acodec libmp3lame -ab 320k "${0/flac/mp3}"' {} \;
Note that this preserves the FLAC files and does not delete them.
The following command:
ffmpeg -i example.mov -r 24 -b:v 256k result.mp4
takes as input example.mov
and lowers the framerate to 24fps
and sets the video bitrate to 256k
.
It appears that scaling the movie down to a smaller resolution increases the filesize instead of lowering it.
The IFS
changes is required in order to process file names with spaces.
OLDIFS=$IFS IFS=$(echo -en "\n\b") for i in *.avi; do ffmpeg -i $i -acodec aac -ac 2 -vcodec libx264 -strict experimental -threads 24 -profile:v baseline -preset ultrafast -level 30 -ab 160k -b 1200k -f mp4 ${i/avi/mp4} done IFS=$OLDIFS
Using -vf
for ffmpeg >= 0.9
:
ffmpeg -i input.mp4 -vf scale=iw/4:-1 output.mp4
where iw
stands for input width
which is then divided by 4
. The -1
indicates that ffmpeg
should preserve the ration and scale the height accordingly.
Sometimes, this will not work because the width and height is not divisible by 2
when using with YUV 4:2:0 chroma subsampling as in the lowest common denominator conversion template. To avoid that, you can issue:
ffmpeg -i input.mp4 -vf "scale=720:trunc(ow/a/2)*2" output.mp4
which will scale the width to 720
and adjust the height according to that value.
Another alternative is to use -2
instead of -1
, as in:
ffmpeg -i input.mp4 -vf scale=iw/4:-2 output.mp4
and it should make sure that the output is divisible by 2.
Using the setps
video filter:
ffmpeg -i input.mp4 -vf setpts=0.5*PTS output.mp4
will double the speed of the movie in the output file.
The following code is an adaptation from Antarctic Nest of Icephoenix and will split clips into equal segments of a given size.
To use, save the file as ffmpeg-split.py
and then run:
chmod +x ffmpeg-split.py
in order to make it executable.
Then, clips can be split using the command:
./ffmpeg-split.py -f someclip.wav -s N
where N
represents the number of seconds that each segment will have.
########################################################################### ## Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 ## ## Please see: http://www.gnu.org/licenses/gpl.html for legal details, ## ## rights of fair usage, the disclaimer and warranty conditions. ## ########################################################################### # Original version can be found at Antarctic Nest of Icephoenix at: # # http://icephoenix.us/notes-for-myself/auto-splitting-video-file-in- # # -equal-chunks-with-ffmpeg-and-python/ # ########################################################################### #!/usr/bin/env python import subprocess import re import math from optparse import OptionParser length_regexp = 'Duration: (\d{2}):(\d{2}):(\d{2})\.\d+,' re_length = re.compile(length_regexp) def main(): (filename, split_length) = parse_options() if split_length <= 0: print "Split length can't be 0" raise SystemExit output = subprocess.Popen("ffmpeg -i '"+filename+"' 2>&1 | grep 'Duration'", shell = True, stdout = subprocess.PIPE ).stdout.read() print output matches = re_length.search(output) if matches: clip_length = int(matches.group(1)) * 3600 + \ int(matches.group(2)) * 60 + \ int(matches.group(3)) print "Clip length in seconds: "+str(clip_length) else: print "Can't determine clip length." raise SystemExit split_count = int(math.ceil(clip_length/float(split_length))) if(split_count == 1): print "Clip length is less then the target split length." raise SystemExit # -acodec copy and -vcodec copy will not give accurate results so do not use them split_cmd = "ffmpeg -i '"+filename+"' " for n in range(0, split_count): split_str = "" if n == 0: split_start = 0 else: split_start = split_length * n split_str += " -ss "+str(split_start)+" -t "+str(split_length) + \ " '"+filename[:-4] + "-" + str(n) + "." + filename[-3:] + \ "'" print "About to run: "+split_cmd+split_str output = subprocess.Popen(split_cmd+split_str, shell = True, stdout = subprocess.PIPE).stdout.read() def parse_options(): parser = OptionParser() parser.add_option("-f", "--file", dest = "filename", help = "file to split, for example sample.avi", type = "string", action = "store" ) parser.add_option("-s", "--split-size", dest = "split_size", help = "split or chunk size in seconds, for example 10", type = "int", action = "store" ) (options, args) = parser.parse_args() if options.filename and options.split_size: return (options.filename, options.split_size) else: parser.print_help() raise SystemExit if __name__ == '__main__': try: main() except Exception, e: print "Exception occured running main():" print str(e)
One of the important changes from the original is that the -acodec
and -vcodec
parameters are not used which guarantees an accurate split down to the frames. By omitting these parameters, ffmpeg
is allowed to re-encode which guarantees that clip lengths are accurate. This is due to the fact that all the frames between key-frames in a clip are a function of those key-frames and if we were to cut between those key-frames then there would not be any information available to decode the clip.
The most compatible video format up to date is MP4:
ffmpeg -i input.mp4 -profile:v 'baseline' -pix_fmt yuv420p \ -r 25 -crf 18 -vcodec libx264 -vb 448k \ -acodec libfdk_aac -ar 44100 -ac 2 -b:a 96k \ -movflags faststart -strict experimental output.mp4
the command uses faststart
that moves the moov
metadata to the beginning of the file so the file can be played while being downloaded.
FRAPs captures videos in AVI format and if you want to use them on OSX, they will have to be converted first using ffmpeg
(which can be installed using homebrew). To convert the video, issue:
ffmpeg -i input.avi -acodec libfdk_aac -b:a 128k -vcodec mpeg4 -b:v 1200k -flags +aic+mv4 output.mp4
libfdk_aac
is the best encoder at the time of writing but it may not be recognised depending on how your ffmpeg
was compiled - if that happens, try libfaac
or aac
.
Trimming metadata contained in a media-file can be accomplished with ffmpeg
by providing the extra switch -map_metadata -1
. This particularly works well with MP3 files that have embedded metadata when creating them under OS X.
ffmpeg -i input.mp4 -c copy -an output.mp4
deb-multimedia packages a full ffpmeg
with all plugins. Add the following lines to /etc/apt/sources.list
:
deb http://www.deb-multimedia.org stable main non-free deb-src http://www.deb-multimedia.org stable main non-free
and then issue:
apt-get update
to update the package list; followed by:
apt-get install deb-multimedia-keyring
to install the keyring for the repo and then update with aptitude
:
aptitude update
Finally, uninstall any existing ffmpeg
:
aptitude purge ffmpeg
and install the new ffmpeg
with:
aptitude install ffmpeg x264
A typical usage case of FFMpeg is to convert a video file into a GIF animation. Assuming an input video named anim.mp4
, the conversion can be performed rather simply by issuing:
ffmpeg -i anim.mp4 anim.gif
where:
anim.mp4
is the input video,anim.gif
is the resulting GIF.Unfortunately, the generate GIF file will be very large and might appear very slow. This is mostly due to ffmpeg trying to maintain the framerate such that it will generate as many frames as necessary.
The first step is to dump all frames of the video file to a GIF file:
ffmpeg -i anim.mp4 -r 1 dump.gif
where:
-r 1
will lower the framerate to 1 frame per secondNow, the resulting file should be very large, so to reduce the size, the obtained file will be converted to another GIF file by lowering the framerate and specifying the amount of frames to skip:
ffmpeg -i dump.gif -vf "setpts=0.25*PTS" -r 7 final.gif
setpts=0.25*PTS
will set the speed as times the current speed,-r 1
will drop or duplicate frames to achieve frames per second
Achieving an acceptable GIF is a games of push and pull between the value of -r
and the value of setpts
.
Other very useful filters are:
decimate
- will merge / drop duplicate frames hence lowering the GIF size,deflicker
- will attempt to stabilize the video,scale
- will scale the movie (down) and hence can be used to lower the file sizeIn one pass:
ffmpeg -i a.gif -vf "deflicker, decimate=cycle=4, fps=8, setpts=0.35*PTS, scale=360:trunc(ow/a/2)*2" b.gif
the actions performed will be:
deflicker
- attempt to deflicker / stabilize the video,decimate=cycle=4
- crop duplicate frames at a rate of duplicate frame out of ,fps=8
- attempt to set the framerate to frames per second,setpts=0.35*PTS
- speed up the movie by of the original speed.
The settings produce an output file with the faststart
flag such that the video can be sought through without having to buffer the entire file.
ffmpeg -i $INPUT_FILE \ -c:v libx264 -crf 23 -profile:v baseline -level 3.0 -pix_fmt yuv420p \ -c:a aac -ac 2 -b:a 128k \ -movflags faststart -movflags separate_moof \ -tune zerolatency output.mp4
Or, with Intel QSV acceleration:
ffmpeg \ -hwaccel qsv \ -hwaccel_output_format qsv \ -i $INPUT_FILE \ -c:v h264_qsv -profile:v baseline -level 3.0 -pix_fmt nv12 \ -c:a aac -ac 2 -b:a 128k \ -movflags faststart -movflags separate_moof \ output.mp4
The following command will capture video from /dev/video0
, at 320x240
resolution and send it to the Linux framebuffer:
ffmpeg -f v4l2 -video_size 320x240 -i /dev/video0 -pix_fmt bgra -f fbdev /dev/fb0
The command can be used, for example, to display a webcam to screen directly without needing to install the X window system.
ffmpeg -i "movie.mp4" -i "movie.srt" -c copy -c:s mov_text output.mp4
where:
movie.mp4
is the movie,movie.srt
are the subtitles in SRT format,output.mp4
is the output movieffmpeg
compiled with –enable-libass
):ffmpeg -i movie.mp4 -vf subtitles=movie.srt output.mp4
or with libass
:
ffmpeg -i movie.srt movie.ass ffmpeg -i movie.mp4 -vf ass=movie.ass output.mp4
where:
movie.mp4
is the movie,movie.srt
are the subtitles in SRT format,output.mp4
is the output movieGiven several files such as:
a.mkv
b.mkv
c.mkv
the files can be concatenated together into one large merged movie by following the steps:
ffmpeg
concatenate filter, assuming that files are named sequentially:for i in $(find . -name \*.mkv); do echo "file '$(realpath $i)'"; done >> list.txt
ffmpeg
:ffmpeg -loglevel info -f concat -safe 0 -i list.txt -c copy "Merged Movie.mkv"
Sometimes it is necessary to normalize the size of multiple video clips. For example, the clips extracted for learning morse code ended up having different sizes that made the video player change size every single letter was displayed.
The following command:
for i in *.mp4; do ffmpeg -i "$i" -vf "crop=w='420':h='420',scale=420:420,setsar=1" conv/"$i"; done
will batch-change the size of all MP4 files in the same directory and store them inside a conv
sub-directory.
This method is called crop-and-scale meaning that the video clip is cropped to a fixed size and then scaled to a fixed size. The only drawback in doing this is that whilst the size of the video clips will be the same for all the clips, the content might be distorted depending on the original video clip.
Issue:
ffmpeg -v trace -i FILE 2>&1 | grep -e type:'\mdat\' -e type:\'moov\'
where:
FILE
is a video fileThis will yield output similar to the following:
mov,mp4,m4a,3gp,3g2,mj2 @ 0x5578f2e712c0] type:'moov' parent:'root' sz: 2586269 40 344228747 [mov,mp4,m4a,3gp,3g2,mj2 @ 0x5578f2e712c0] type:'mdat' parent:'root' sz: 341642438 2586317 344228747
If moov
appears before mdat
then the faststart
flag is set on the video file.
It seems that FFMpeg version 6.1.1 is a version that is suitable for Windows and higher version numbers might crash with a memory access violation error 0xc0000005
.
When ffmpeg is reading from a pipeline, for instance, by using the -i pipe:
flag, the input has a set buffer that ffmpeg uses before performing any transcoding. It might happen that the set buffer is insufficient, which leads to conversion failures, spurious errors for various reasons and the resulting file containing distortions. For example, using ffmpeg with a HDHomeRun input stream, as in:
ffmpeg -y -i http://192.168.1.10:5004/tuner3/ch390000000-10 -c:v libx264 -c:a aac -ac 2 -b:a 128k -movflags +faststart -tune zerolatency /output.mp4
results in several small errors along the lines of:
frame= 1015 fps= 11 q=21.0 size= 4352kB time=00:00:40.56 bitrate= 879.0kbits/frame= 1022 fps= 11 q=21.0 size= 4608kB time=00:00:40.84 bitrate= 924.3kbits/[mpegts @ 0x55f2c471b2c0] PES packet size mismatch [mpegts @ 0x55f2c471b2c0] Packet corrupt (stream = 1, dts = 4597918858). pipe:: corrupt input packet in stream 1 Last message repeated 2 times [mp2 @ 0x55f2c4755ec0] Header missing Error while decoding stream #0:1: Invalid data found when processing input [mpeg2video @ 0x55f2c4754300] ac-tex damaged at 30 23 [mpeg2video @ 0x55f2c4754300] Warning MVs not available [mpeg2video @ 0x55f2c4754300] concealing 585 DC, 585 AC, 585 MV errors in I frame pipe:: corrupt decoded frame in stream 0 frame= 1226 fps= 13 q=19.0 size= 4864kB time=00:00:49.00 bitrate= 813.2kbits/frame= 1227 fps= 12 q=25.0 size= 4864kB time=00:00:49.04 bitrate= 812.5kbits/frame= 1231 fps= 12 q=25.0 size= 4864kB time=00:00:49.20 bitrate= 809.9kbits/
being printed to the console output.
Intuitively these are buffer-underrun errors that are due to the small internal ffmpeg buffer size. In order to fix these issues, specify an queue size on the command line:
ffmpeg -thread_queue_size 8192 -y -i http://192.168.1.10:5004/tuner3/ch390000000-10 -c:v libx264 -c:a aac -ac 2 -b:a 128k -movflags +faststart -tune zerolatency /output.mp4
where:
-thread_queue_size
specifies the internal queue size expressed in bytesFortunately, if the queue is not enough and buffer underruns are detected, ffmpeg will print out a warning:
Thread message queue blocking; consider raising the thread_queue_size option (current value: 8192)
such that on the next invocation, the ffmpeg command parameters can be adjusted and the queue increased.
Webcams are cheap equipment these days with various performance issues and quirks for every producer out there. There are some general guidelines that should be minded when recording live webcam streams.
The immediate *nix reflex is to jump onto "ffmpeg" and start generating a long line to access the live stream, transcode it to a desired format and then store it onto the drive. Unfortunately, the problem is that for complex operations such as transcoding, "ffmpeg" will eat a whole lot of CPU power and/or GPU power. Whilst the former is not as important given commodity hardware that is cheaper and cheaper, sometimes there is simply no need to perform a transcoding task because the camera already provides a stream with an optimized and universal format.
For instance, running the command ffprobe -i rtsp://...
against a D-Link Tapo camera, will return a video stream codec type of h264
and audio encoded in pcm_alaw
, both of which can just be dumped directly to an MKV container file without any processing:
ffmpeg -i rtsp://... -c:v copy -c:a copy out.mkv
Or, perhaps with some little streaming optimizations that do not affect CPU nor GPU power because they just change the way how markers are added to the saved video files:
ffmpeg -i rtsp://... -c:v copy -c:a copy -movflags faststart -movflags separate_moof -tune zerolatency out.mkv
The former commands will generate zero CPU or GPU overhead, whilst recording an RTSP stream that is already provided with universal codecs. When in doubt, fprobe
should be first used on the camera to make sure that the camera does not already provide something suitable such that transcoding is not needed.
Video editing tools typically have the ability to draw on top of the video. Even "ffmpeg", and in spite that it is used on the command line with no GUI, has some editing tools such as drawtext
that allows text to be overlayed on top of the video. Unfortunately, following the example in the section before, regardless whether the output will still be H264, transcoding will be needed and the command changes to:
ffmpeg -i rtsp://... -c:v libx264 -c:a copy -vf "drawtext..." -movflags faststart -movflags separate_moof -tune zerolatency out.mkv
and now "ffmpeg" will start using the CPU and GPU to both overlay the text and transcode to the final result.
However, if just annotations are needed, subtitles could be used instead, such that a subtitle file can be generated in parallel to the recording of the live stream and then be read automatically when the recorded file is loaded. For instance, the script snippet in the bash section is capable of generating a subtitle file in real time by also polling a file in real time.
Not only are subtitles more efficient, but it also seems fairly canonical to have subtitles (or annotations) separate from the video recording instead of just drawing the text onto the video. If this strategy is adopted, the files are loaded together by any media player, but they can also be read separately.
The following flow uses two "exec" nodes, one to launch "ffmpeg" and record the RTSP stream to a local file by copying the video and audio codecs without transcoding and without hogging the CPU or GPU, whilst the other "exec" node launches a process in the background that reads a file and generates a subtitle file. Both the video and the subtitle file are named similarly as the basename with only the prefix varying from MKV
to SUB
such that opening up the video MKV file with any player should make the player automatically load the subtitle file.
[{"id":"def4582d2e2d067d","type":"group","z":"01bf6772c1feb7f4","g":"ab8078c26ea86566","name":"Recording","style":{"label":true},"nodes":["a69c011e5ebab4da","58d245ffd8d671f1","8423cd253b595171","ce0d715d97c16df1","1918f6c6249a9122","49fe07f39d609066","8708c7e8198f347a","d3d1499402365cce","bde51c6e8d3fc3dd","94568eddcaaee36f","83574efd30e1f858"],"x":54,"y":419,"w":552,"h":302},{"id":"a69c011e5ebab4da","type":"function","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"record","func":"msg={}\nmsg.outputFile=`/projects/cameras/external/auto/${moment().format('YYYYMMDDHHmmss')}.mkv`\nmsg.payload=`ffmpeg -y -i rtsp://.../stream2 -c:v copy -c:a copy -movflags faststart -movflags separate_moof -tune zerolatency ${msg.outputFile}\"`\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"moment","module":"moment"},{"var":"crypto","module":"crypto"}],"x":310,"y":540,"wires":[["8423cd253b595171","49fe07f39d609066"]]},{"id":"58d245ffd8d671f1","type":"function","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"stop","func":"msg = {}\nmsg.kill = \"SIGHUP\"\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":660,"wires":[["49fe07f39d609066","94568eddcaaee36f"]]},{"id":"8423cd253b595171","type":"debug","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"debug 64","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":500,"y":460,"wires":[]},{"id":"ce0d715d97c16df1","type":"inject","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":540,"wires":[["a69c011e5ebab4da","83574efd30e1f858"]]},{"id":"1918f6c6249a9122","type":"inject","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":600,"wires":[["58d245ffd8d671f1"]]},{"id":"49fe07f39d609066","type":"exec","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","command":"","addpay":"payload","append":"","useSpawn":"true","timer":"","winHide":true,"oldrc":false,"name":"","x":490,"y":540,"wires":[["8423cd253b595171"],["8423cd253b595171"],["8423cd253b595171"]]},{"id":"8708c7e8198f347a","type":"link in","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"link in 23","links":["437c598d34158341"],"x":195,"y":480,"wires":[["a69c011e5ebab4da","83574efd30e1f858"]]},{"id":"d3d1499402365cce","type":"link in","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"link in 24","links":["2b231636c40b06f9"],"x":205,"y":680,"wires":[["58d245ffd8d671f1"]]},{"id":"bde51c6e8d3fc3dd","type":"debug","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"debug 80","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":500,"y":680,"wires":[]},{"id":"94568eddcaaee36f","type":"exec","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","command":"","addpay":"payload","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"","x":490,"y":620,"wires":[["bde51c6e8d3fc3dd"],["bde51c6e8d3fc3dd"],["bde51c6e8d3fc3dd"]]},{"id":"83574efd30e1f858","type":"function","z":"01bf6772c1feb7f4","g":"def4582d2e2d067d","name":"subtitle","func":"msg = {}\nmsg.outputFile = `/projects/cameras/external/auto/${moment().format('YYYYMMDDHHmmss')}.sub`\nmsg.payload = `COUNT=0 && while [ 1 ]; do while read i; do echo -e \"$COUNT\\n$(date -d@$COUNT -u +%H:%M:%S,000) --> $(date -d@$((COUNT+1)) -u +%H:%M:%S,000)\\n$i\\n\" && COUNT=$((COUNT+1)) && sleep 1; done </projects/sensor-cocktail/actual/noise.txt; done >> ${msg.outputFile}`\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"moment","module":"moment"}],"x":320,"y":600,"wires":[["94568eddcaaee36f"]]}]