{"course":{"slug":"motion-and-audio","title":"Motion, Sound and Music","url":"https://addaly.com/learn/motion-and-audio","level":"intermediate","track":"create"},"lesson":{"slug":"ffmpeg-the-free-path","title":"The tool under most of the others","url":"https://addaly.com/learn/motion-and-audio/ffmpeg-the-free-path","position":45,"of":72,"minutes":10,"answer":"Options before -i apply to reading and options after apply to writing, -c copy trims and joins with no decode and no loss.","keyIdea":"Options before -i apply to reading and options after apply to writing, -c copy trims and joins with no decode and no loss, and -movflags +faststart moves the MP4 index to the front so a web video can start before it has finished downloading."},"body":{"format":"markdown","text":"## One tool under most of the others\n\nffmpeg is the free software that decodes, encodes, filters and converts essentially every audio and video format. It is also what a great many commercial applications use internally, which means learning it is not learning an alternative to the professional tools: it is learning the layer most of them sit on.\n\nThe command line puts people off. It is worth twenty minutes, because a handful of commands cover most of what an editor is slow at.\n\n## The shape of a command\n\n```\nffmpeg  [input options]  -i input  [output options]  output\n```\n\nOptions before `-i` apply to reading the input; options after apply to writing the output. Getting that the wrong way round is the most common beginner error, and `-ss` is where it shows: before `-i` it seeks quickly, after `-i` it decodes and discards.\n\n## Trim without re-encoding\n\n```bash\nffmpeg -ss 00:01:30 -i in.mp4 -t 00:00:45 -c copy out.mp4\n```\n\nNo decode, no encode, no quality loss, done in under a second on a long file. The cut lands on the nearest keyframe, so the start may be up to a GOP early. For a precise cut you have to re-encode:\n\n```bash\nffmpeg -ss 00:01:30 -i in.mp4 -t 00:00:45 -c:v libx264 -crf 18 -c:a aac out.mp4\n```\n\n## Scale, with the trap included\n\n```bash\nffmpeg -i in.mp4 -vf \"scale=1280:-2\" -c:v libx264 -crf 21 -c:a copy out.mp4\n```\n\n`-2` means \"work out the height to preserve the aspect ratio, and make it divisible by 2\". `-1` preserves aspect but can produce an odd number, which H.264's 4:2:0 chroma cannot represent: that is the source of the \"width not divisible by 2\" error everybody meets once.\n\nFor a high-quality downscale, add `flags=lanczos`. For upscaling, nothing in ffmpeg invents detail; a good scaler is just less soft than a bad one.\n\n## Join clips\n\nSame codec and parameters, no re-encode:\n\n```bash\nprintf \"file 'a.mp4'\\nfile 'b.mp4'\\nfile 'c.mp4'\\n\" > list.txt\nffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4\n```\n\nDifferent sources have to be re-encoded, via the `concat` filter, and it is worth normalising them to the same resolution and frame rate first rather than letting the filter improvise.\n\n## Extract and rebuild frames\n\n```bash\nffmpeg -i in.mp4 -vf fps=12 frames/f_%04d.png\nffmpeg -framerate 12 -i frames/f_%04d.png -c:v libx264 -crf 18 -pix_fmt yuv420p out.mp4\n```\n\n`-pix_fmt yuv420p` on the way out is not optional if the file has to play in browsers and on televisions; without it, ffmpeg may write 4:4:4 from PNG input and many hardware decoders will refuse it.\n\n## Audio jobs\n\n```bash\n# extract audio untouched\nffmpeg -i in.mp4 -vn -c:a copy audio.m4a\n\n# replace the audio track\nffmpeg -i video.mp4 -i new.wav -map 0:v -map 1:a -c:v copy -c:a aac -shortest out.mp4\n\n# normalise loudness to -14 LUFS, two-pass\nffmpeg -i in.wav -af loudnorm=I=-14:TP=-1.5:LRA=11:print_format=summary -f null -\nffmpeg -i in.wav -af loudnorm=I=-14:TP=-1.5:LRA=11:measured_I=...:measured_TP=... out.wav\n```\n\n`loudnorm` in a single pass does a dynamic normalisation that can pump. Two-pass (measure, then apply the measured values) does a linear gain change and is what you want. The mixing module covers what those numbers mean.\n\n## Inspecting a file\n\n```bash\nffprobe -v error -show_streams -show_format in.mp4\nffprobe -v error -select_streams v:0 -show_entries \\\n  stream=codec_name,width,height,r_frame_rate,pix_fmt,color_range,bit_rate \\\n  -of default=nw=1 in.mp4\n```\n\nMost delivery arguments end the moment somebody runs this. **MediaInfo** presents the same information in a graphical window and is also free.\n\n## Three flags worth memorising\n\n- `-movflags +faststart`: moves the MP4 index to the front so the file streams. Non-negotiable for web.\n- `-pix_fmt yuv420p`: maximum compatibility.\n- `-c copy`: no re-encode, no loss, near-instant. Try it first for anything that does not change the pictures.\n\n## Batching\n\nA loop over a folder is where ffmpeg earns its keep against a graphical editor:\n\n```bash\nfor f in *.mov; do\n  ffmpeg -i \"$f\" -vf \"scale=-2:720\" -c:v libx264 -crf 22 \\\n    -c:a aac -b:a 128k -movflags +faststart \"web/${f%.mov}.mp4\"\ndone\n```\n\nForty files while you make tea. The same job in an editor is forty exports and forty chances to get one setting wrong.\n\n## The free path, and the front ends\n\nffmpeg itself is free under LGPL or GPL depending on build. **HandBrake** wraps the same encoders with a graphical interface and sensible presets. **Shutter Encoder** is a free front end aimed at editors with named jobs rather than flags. **MediaInfo** reads files. **Losslesscut** trims with `-c copy` behind a timeline interface. None of these is a lesser version of a paid tool; they are the tool.\n\n## The limitation\n\nffmpeg does exactly what you say, which includes silently doing something unhelpful. Omit `-c:a` and it re-encodes your audio; omit `-pix_fmt` and it may write something unplayable; get the order of `-ss` wrong and you wait ten minutes for a seek. It has no preview, no undo, and its error messages assume you know the vocabulary.\n\nIt also will not make creative decisions. It cannot tell you that the CRF is too high for this footage or that the crop lost somebody's head. It is a precise instrument for jobs you can already specify, and the judgement stays with you."},"check":{"question":"A team reports that their web video does not begin playing until it is fully downloaded, although the server supports range requests and the file is encoded at a modest bitrate. What is the most likely cause?","options":[{"text":"The keyframe interval is too long, so the player has to buffer an entire GOP before it can present the first frame.","correct":false,"tempting":"A long GOP delays seeking to arbitrary points, but the first keyframe is at the start of the file."},{"text":"The moov atom holding the file's index was written at the end, so the player cannot begin decoding until it has the whole file.","correct":true},{"text":"The pixel format is 4:4:4 rather than yuv420p, so the browser is falling back to a software decoder that buffers the stream before playback.","correct":false,"tempting":"An unsupported pixel format usually means the video does not play at all rather than playing late."},{"text":"The file was encoded with two-pass rate control, which writes a variable-bitrate stream whose per-segment sizes are only knowable from the completed log, so a player performing range requests cannot determine which byte range corresponds to the opening seconds until it has read to the end.","correct":false,"tempting":"Attributes to rate control what is really an index-placement problem; players locate byte ranges from the index, not from the rate-control mode."}]},"prev":{"slug":"exporting-without-a-huge-file","title":"Your hero video is why the page feels slow","url":"https://addaly.com/learn/motion-and-audio/exporting-without-a-huge-file"},"next":{"slug":"what-a-microphone-measures","title":"Pressure, gradient, and everything that follows","url":"https://addaly.com/learn/motion-and-audio/what-a-microphone-measures"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The tool under most of the others', in Motion, Sound and Music. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/motion-and-audio/ffmpeg-the-free-path (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The tool under most of the others","courseTitle":"Motion, Sound and Music","module":{"index":5,"slug":"codecs-colour-and-delivery","title":"Codecs, colour and delivery"},"answer":null,"keyIdea":"Options before -i apply to reading and options after apply to writing, -c copy trims and joins with no decode and no loss, and -movflags +faststart moves the MP4 index to the front so a web video can start before it has finished downloading.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"one-tool-under-most-of-the-others","runs":[{"text":"One tool under most of the others"}]},{"t":"p","runs":[{"text":"ffmpeg is the free software that decodes, encodes, filters and converts essentially every audio and video format. It is also what a great many commercial applications use internally, which means learning it is not learning an alternative to the professional tools: it is learning the layer most of them sit on."}]},{"t":"p","runs":[{"text":"The command line puts people off. It is worth twenty minutes, because a handful of commands cover most of what an editor is slow at."}]},{"t":"h2","id":"the-shape-of-a-command","runs":[{"text":"The shape of a command"}]},{"t":"code","lang":"","label":"Text","text":"ffmpeg  [input options]  -i input  [output options]  output"},{"t":"p","runs":[{"text":"Options before "},{"text":"-i","code":true},{"text":" apply to reading the input; options after apply to writing the output. Getting that the wrong way round is the most common beginner error, and "},{"text":"-ss","code":true},{"text":" is where it shows: before "},{"text":"-i","code":true},{"text":" it seeks quickly, after "},{"text":"-i","code":true},{"text":" it decodes and discards."}]},{"t":"h2","id":"trim-without-re-encoding","runs":[{"text":"Trim without re-encoding"}]},{"t":"code","lang":"bash","label":"Bash","text":"ffmpeg -ss 00:01:30 -i in.mp4 -t 00:00:45 -c copy out.mp4"},{"t":"p","runs":[{"text":"No decode, no encode, no quality loss, done in under a second on a long file. The cut lands on the nearest keyframe, so the start may be up to a GOP early. For a precise cut you have to re-encode:"}]},{"t":"code","lang":"bash","label":"Bash","text":"ffmpeg -ss 00:01:30 -i in.mp4 -t 00:00:45 -c:v libx264 -crf 18 -c:a aac out.mp4"},{"t":"h2","id":"scale-with-the-trap-included","runs":[{"text":"Scale, with the trap included"}]},{"t":"code","lang":"bash","label":"Bash","text":"ffmpeg -i in.mp4 -vf \"scale=1280:-2\" -c:v libx264 -crf 21 -c:a copy out.mp4"},{"t":"p","runs":[{"text":"-2","code":true},{"text":" means “work out the height to preserve the aspect ratio, and make it divisible by 2”. "},{"text":"-1","code":true},{"text":" preserves aspect but can produce an odd number, which H.264’s 4:2:0 chroma cannot represent: that is the source of the “width not divisible by 2” error everybody meets once."}]},{"t":"p","runs":[{"text":"For a high-quality downscale, add "},{"text":"flags=lanczos","code":true},{"text":". For upscaling, nothing in ffmpeg invents detail; a good scaler is just less soft than a bad one."}]},{"t":"h2","id":"join-clips","runs":[{"text":"Join clips"}]},{"t":"p","runs":[{"text":"Same codec and parameters, no re-encode:"}]},{"t":"code","lang":"bash","label":"Bash","text":"printf \"file 'a.mp4'\\nfile 'b.mp4'\\nfile 'c.mp4'\\n\" > list.txt\nffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4"},{"t":"p","runs":[{"text":"Different sources have to be re-encoded, via the "},{"text":"concat","code":true},{"text":" filter, and it is worth normalising them to the same resolution and frame rate first rather than letting the filter improvise."}]},{"t":"h2","id":"extract-and-rebuild-frames","runs":[{"text":"Extract and rebuild frames"}]},{"t":"code","lang":"bash","label":"Bash","text":"ffmpeg -i in.mp4 -vf fps=12 frames/f_%04d.png\nffmpeg -framerate 12 -i frames/f_%04d.png -c:v libx264 -crf 18 -pix_fmt yuv420p out.mp4"},{"t":"p","runs":[{"text":"-pix_fmt yuv420p","code":true},{"text":" on the way out is not optional if the file has to play in browsers and on televisions; without it, ffmpeg may write 4:4:4 from PNG input and many hardware decoders will refuse it."}]},{"t":"h2","id":"audio-jobs","runs":[{"text":"Audio jobs"}]},{"t":"code","lang":"bash","label":"Bash","text":"# extract audio untouched\nffmpeg -i in.mp4 -vn -c:a copy audio.m4a\n\n# replace the audio track\nffmpeg -i video.mp4 -i new.wav -map 0:v -map 1:a -c:v copy -c:a aac -shortest out.mp4\n\n# normalise loudness to -14 LUFS, two-pass\nffmpeg -i in.wav -af loudnorm=I=-14:TP=-1.5:LRA=11:print_format=summary -f null -\nffmpeg -i in.wav -af loudnorm=I=-14:TP=-1.5:LRA=11:measured_I=...:measured_TP=... out.wav"},{"t":"p","runs":[{"text":"loudnorm","code":true},{"text":" in a single pass does a dynamic normalisation that can pump. Two-pass (measure, then apply the measured values) does a linear gain change and is what you want. The mixing module covers what those numbers mean."}]},{"t":"h2","id":"inspecting-a-file","runs":[{"text":"Inspecting a file"}]},{"t":"code","lang":"bash","label":"Bash","text":"ffprobe -v error -show_streams -show_format in.mp4\nffprobe -v error -select_streams v:0 -show_entries \\\n  stream=codec_name,width,height,r_frame_rate,pix_fmt,color_range,bit_rate \\\n  -of default=nw=1 in.mp4"},{"t":"p","runs":[{"text":"Most delivery arguments end the moment somebody runs this. "},{"text":"MediaInfo","b":true},{"text":" presents the same information in a graphical window and is also free."}]},{"t":"h2","id":"three-flags-worth-memorising","runs":[{"text":"Three flags worth memorising"}]},{"t":"ul","items":[[{"text":"-movflags +faststart","code":true},{"text":": moves the MP4 index to the front so the file streams. Non-negotiable for web."}],[{"text":"-pix_fmt yuv420p","code":true},{"text":": maximum compatibility."}],[{"text":"-c copy","code":true},{"text":": no re-encode, no loss, near-instant. Try it first for anything that does not change the pictures."}]]},{"t":"h2","id":"batching","runs":[{"text":"Batching"}]},{"t":"p","runs":[{"text":"A loop over a folder is where ffmpeg earns its keep against a graphical editor:"}]},{"t":"code","lang":"bash","label":"Bash","text":"for f in *.mov; do\n  ffmpeg -i \"$f\" -vf \"scale=-2:720\" -c:v libx264 -crf 22 \\\n    -c:a aac -b:a 128k -movflags +faststart \"web/${f%.mov}.mp4\"\ndone"},{"t":"p","runs":[{"text":"Forty files while you make tea. The same job in an editor is forty exports and forty chances to get one setting wrong."}]},{"t":"h2","id":"the-free-path-and-the-front-ends","runs":[{"text":"The free path, and the front ends"}],"role":"free"},{"t":"p","runs":[{"text":"ffmpeg itself is free under LGPL or GPL depending on build. "},{"text":"HandBrake","b":true},{"text":" wraps the same encoders with a graphical interface and sensible presets. "},{"text":"Shutter Encoder","b":true},{"text":" is a free front end aimed at editors with named jobs rather than flags. "},{"text":"MediaInfo","b":true},{"text":" reads files. "},{"text":"Losslesscut","b":true},{"text":" trims with "},{"text":"-c copy","code":true},{"text":" behind a timeline interface. None of these is a lesser version of a paid tool; they are the tool."}]},{"t":"h2","id":"the-limitation","runs":[{"text":"The limitation"}],"role":"limit"},{"t":"p","runs":[{"text":"ffmpeg does exactly what you say, which includes silently doing something unhelpful. Omit "},{"text":"-c:a","code":true},{"text":" and it re-encodes your audio; omit "},{"text":"-pix_fmt","code":true},{"text":" and it may write something unplayable; get the order of "},{"text":"-ss","code":true},{"text":" wrong and you wait ten minutes for a seek. It has no preview, no undo, and its error messages assume you know the vocabulary."}]},{"t":"p","runs":[{"text":"It also will not make creative decisions. It cannot tell you that the CRF is too high for this footage or that the crop lost somebody’s head. It is a precise instrument for jobs you can already specify, and the judgement stays with you."}]}],"check":{"question":[{"text":"A team reports that their web video does not begin playing until it is fully downloaded, although the server supports range requests and the file is encoded at a modest bitrate. What is the most likely cause?"}],"options":[{"text":[{"text":"The keyframe interval is too long, so the player has to buffer an entire GOP before it can present the first frame."}],"correct":false,"tempting":[{"text":"A long GOP delays seeking to arbitrary points, but the first keyframe is at the start of the file."}]},{"text":[{"text":"The moov atom holding the file’s index was written at the end, so the player cannot begin decoding until it has the whole file."}],"correct":true,"tempting":null},{"text":[{"text":"The pixel format is 4:4:4 rather than yuv420p, so the browser is falling back to a software decoder that buffers the stream before playback."}],"correct":false,"tempting":[{"text":"An unsupported pixel format usually means the video does not play at all rather than playing late."}]},{"text":[{"text":"The file was encoded with two-pass rate control, which writes a variable-bitrate stream whose per-segment sizes are only knowable from the completed log, so a player performing range requests cannot determine which byte range corresponds to the opening seconds until it has read to the end."}],"correct":false,"tempting":[{"text":"Attributes to rate control what is really an index-placement problem; players locate byte ranges from the index, not from the rate-control mode."}]}],"order":[3,2,1,0],"why":null},"prev":{"slug":"exporting-without-a-huge-file","title":"Your hero video is why the page feels slow","minutes":10},"next":{"slug":"what-a-microphone-measures","title":"Pressure, gradient, and everything that follows","minutes":10}}}