Table of Contents

Terminology

Cropping and cutting have the same meaning but movie processing software seems to distinguish between the two:

Crop a Movie

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.

Trimming a Movie

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.

Convert FLAC to MP3 Recursively

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.

Lowering the Quality of Movies

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.

Convert Movies to iPad Format

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

Scale Movies

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.

Speed-up Movies

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.

Split Clips into Equal Segments of a Given Size

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.

Preparing Video for Web Serving

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.

Converting FRAPs Videos

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.

Trim Metadata

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.

Strip Audio from Video Clip

ffmpeg -i input.mp4 -c copy -an output.mp4

Install a Full FFmpeg Distribution on Debian

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

Convert Video to GIF Animation

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:

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:

Now, 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

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:

In 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:

Lowest Common Denominator Settings Compatible with All Sites

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

Capture WebCam to Framebuffer

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.

Adding Subtitles to Videos

ffmpeg -i "movie.mp4" -i "movie.srt" -c copy -c:s mov_text output.mp4

where:

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:

Concatenating or Merging Multiple Files

Given several files such as:

the files can be concatenated together into one large merged movie by following the steps:

for i in $(find . -name \*.mkv); do echo "file '$(realpath $i)'"; done >> list.txt
ffmpeg -loglevel info -f concat -safe 0 -i list.txt -c copy "Merged Movie.mkv"

Normalizing the Size of Video Clips

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.

Determining if A Video File has Fast Start Enabled

Issue:

ffmpeg -v trace -i FILE 2>&1 | grep -e type:'\mdat\' -e type:\'moov\'

where:

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

Windows 7 Compatible Builds

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.

Increase Input Buffering When Reading from a Pipeline

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:

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

Strategies for Recording Live Webcam Streams

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.

Transcoding

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.

Annotations

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.

Node-Red Flow

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"]]}]