Tutorials Developer

FFmpeg Command Patterns Worth Knowing

By Infinite Creation · Intermediate · ~20 min
A 1960s mid-century comic book style editorial illustration showing two engineers at terminals in a machine room, with a phone clamped to a rig, a bank of encoders visible in the frame.

Overview

FFmpeg underlies most media processing, and its reputation for difficulty comes largely from a few concepts that are not obvious from the documentation, particularly stream mapping and the difference between copying and re-encoding. Once those land, a small number of command shapes cover the large majority of real work. This guide covers those patterns and the specific behaviours that produce surprising output.

What You Need

  • FFmpeg installed, with the encoders you need compiled in
  • A way to inspect files, ffprobe, which ships alongside it
  • Test media that is deliberately awkward, not just clean exports
  • Awareness of which build you have, since features vary
  • Somewhere to record the exact commands you run
  • Patience with the argument order, which is positional and matters

Steps

1

Probe before you process

Inspect the input first: how many streams, which codecs, what resolution and frame rate, what channel layout, and whether rotation metadata is present. Most surprising output comes from an input that was not what you assumed. Making probing a required first step turns mysterious failures into obvious ones.

2

Learn the difference between copying and re-encoding

Stream copy passes compressed data through untouched, fast, lossless, and only possible when the output container accepts that codec. Re-encoding decodes and compresses again, which is slower and loses quality. Changing container without changing codec should always be a copy. People routinely re-encode by accident and lose quality for no reason.

3

Be explicit about stream mapping

Without instruction, FFmpeg picks one stream of each type by its own rules, which quietly discards additional audio tracks, subtitles, and data streams. If your input has multiple audio tracks and your output has one, this is why. Specify which streams you want explicitly rather than relying on the defaults.

4

Order arguments correctly, because position is meaningful

Options before an input apply to that input. Options after apply to the output. The same flag means different things in different positions, and misplacing it is a frequent source of commands that appear to be ignored. When something has no effect, check its position before assuming the flag is wrong.

5

Handle rotation and odd dimensions deliberately

Phone video frequently carries rotation in metadata rather than in the pixels, so naive processing produces sideways output. Some encoders also require even dimensions and fail on odd ones. Both are extremely common with user-supplied media and both are best handled explicitly in your pipeline.

6

Log the exact command and toolchain version

Record what you ran and which build ran it, on every job. Behaviour differs between versions and builds, and when output changes character months later this log is the only way to establish what actually changed. It converts an unreproducible report into a reproducible case.

Pro Tips

  • Probe every input. Assumptions about user-supplied media are wrong more often than not.
  • Copy rather than re-encode whenever the container change is all you need.
  • Map streams explicitly. Silent stream dropping is the most common surprise in FFmpeg output.
  • Pin the version in a container. Behaviour genuinely differs between builds.
  • Test with variable frame rate footage. It is common from phones and screen recorders and breaks naive assumptions.

Tested: what the page's FFmpeg patterns do when you run them

We ran each behaviour this page describes on 2026-09-29 with FFmpeg 8.1.2 and ffprobe, on media generated for the purpose, and read the results back with ffprobe. Nothing here was timed.

Claim on the pageWhat we ranWhat came out
Step 2: copying leaves the data untouchedThe video stream copied from Matroska to MP4, then re-encoded with libx264 at CRF 18. Packet hash and PSNR against the sourceCopy: identical hash and infinite PSNR. Re-encode: different hash, 28.7 dB average PSNR
Step 3: default mapping drops streamsA Matroska file with 2 video, 3 audio, 1 subtitle, converted with no -mapTo MP4: 1 video, 1 audio, 0 subtitle. To Matroska: 1 video, 1 audio, 1 subtitle. With -map 0 and copy: all six
Step 4: position changes meaning-ss, -to, -r and -s placed before and after -i-to 4 with -ss 3: 3.0 s before, 1.0 s after. -r 10: 180 frames over 18.0 s before, 62 over 6.2 s after. -s before -i failed
Step 5: rotation is metadataA 640 by 360 file tagged with 90 degree rotation, copied and re-encoded three waysCopy: 640 by 360 with the tag kept. Re-encode: 360 by 640 with no tag. With -noautorotate: 640 by 360. Adding a transpose in the same direction as the tag: 640 by 360, turned 180 degrees. In the opposite direction: the raw orientation again
Step 5: odd dimensions failA 641 by 361 picture sent to six encoders as 4:2:0 (4:2:2 for ProRes)Failed: libx264, libx265. Accepted at 641 by 361: libvpx-vp9, mpeg4, prores_ks. Silently cropped to 640 by 360: h264_videotoolbox
Variable frame rate breaks constant rate timing4 s at 30 fps then 4 s at 10 fps, with a tone for 8 s, frame times read backffprobe reports a rate of 30/1 and an average of 30/1, though the frames average 20 fps. Frame 159 sits at 7.90 s where a 30 fps grid puts it at 5.30 s

Steps 2 and 3 hold, with one correction for subtitles. Copying gave an identical packet hash and re-encoding at CRF 18 did not, with a PSNR of 28.7 dB against the source. That number comes from a busy synthetic pattern that was already compressed once, so it shows that quality is lost, not how much a real video would lose. On mapping, the MP4 came out with one video and one audio stream of the six. The Matroska output kept the subtitle track as well, so the page's "subtitle streams disappear" depends on the container. Two audio tracks and the second video stream were dropped in both, and nothing was printed at the default log level.

Step 4 is more than the page says. Position did not matter for -ss on a re-encode, both gave 1.0 s. It mattered a lot for -to, because a seek before the input restarts the clock, so the same -to 4 stopped at 3.0 s in one command and 1.0 s in the other. Before -i, -r told FFmpeg how to read the file and stretched it to 18.0 s. After, it dropped frames. -s before -i did not run at all: "Option video_size not found". So a misplaced flag was ignored in no case here. It either changed the meaning or failed loudly.

Rotation behaves as the page says. The file stores 640 by 360 pixels and a 90 degree tag. Applying the rotation and then a transpose in the same direction gave a frame that matched a 180 degree turn of the untouched picture (mean pixel difference 0.4, against 111.8 for no turn), which is the page's upside down result. A transpose in the opposite direction cancelled the rotation and gave the raw orientation back (difference 0.0), so what a second rotation does depends on which way it turns.

Odd sizes are worse than the page says, because failing is not the only outcome. libx264 and libx265 refused the picture ("width not divisible by 2 (641x361)"). Given 4:4:4 instead, libx264 accepted it at 641 by 361, so the limit comes from the chroma subsampling. h264_videotoolbox accepted it and wrote 640 by 360, losing a row and a column with no message. Scaling to an even size first, with trunc(iw/2)*2, gave 640 by 360 with libx264 in 4:2:0.

Variable frame rate is real, and ffprobe's headline rates did not flag it. The file's last frame is at 7.90 s and a 30 fps grid puts frame 159 at 5.30 s, so a pipeline that assumes 30 fps is 2.6 s off by the end, which is how far the video would fall out of step with the 8 s tone. A file with a Matroska container named .mp4 was read as matroska,webm without complaint, so the extension told us nothing.

Map everything, copy it, and check the result: ffmpeg -i in.mkv -map 0 -c copy out.mkv && ffprobe -v error -show_entries stream=index,codec_type -of csv out.mkv

Validated. Run 2026-09-29 with FFmpeg 8.1.2 and ffprobe on generated test media. The results are for this build. Encoders vary between builds, and h264_videotoolbox exists only where the platform provides it. The PSNR is from a synthetic pattern. We did not test probing as a habit, channel layouts, other containers, real phone footage or another FFmpeg version.

What You'll Learn

FFmpeg is not difficult so much as unforgiving about a few specific concepts. Below: why stream mapping causes so much trouble, and the input characteristics that break naive pipelines.

Why Stream Mapping Causes So Much Trouble

A media file can contain many streams: several video, several audio in different languages or channel layouts, subtitles, chapters, and data. FFmpeg has to decide which of them end up in the output.

Left unspecified, it applies default selection rules, broadly, the best single stream of each type by its own criteria. That is convenient for simple cases and silently destructive for anything else. Multiple audio tracks become one. Subtitle streams disappear. A second video stream used as a thumbnail vanishes.

Nothing warns you, because from FFmpeg's perspective it did what it was designed to do. The symptom appears later as "the localised audio track is missing", frequently after processing has been running in production for weeks.

The discipline is to be explicit about which streams you want in every command that matters, and to verify the output by probing rather than by assuming. In a pipeline handling user-supplied media, mapping should never be left to defaults, because you do not know what the inputs contain.

The Input Characteristics That Break Naive Pipelines

User-supplied media is reliably stranger than test media, and the same few characteristics cause most failures.

Variable frame rate. Phone cameras and screen recorders frequently produce footage with no constant frame rate. Pipelines assuming constancy produce audio drift or timing errors that grow across a long file.

Rotation in metadata. The pixels are stored one way and a flag says to display them rotated. Processing that ignores the flag produces sideways output. Processing that applies it twice produces upside-down output.

Unexpected channel layouts. Audio arriving as mono, as multichannel, or with silent channels breaks assumptions about stream mapping and loudness.

Container and codec mismatch with the extension. A file named .mp4 that is something else entirely is common.

Odd dimensions. Some encoders require even width and height and fail outright.

A fixture set containing all of these, run through your pipeline in tests, catches the overwhelming majority of production failures before they happen.

Where This Fits

FFmpeg does the processing, and Building Media Applications covers the upload and job architecture around it.

FAQ

Q: When should I copy streams instead of re-encoding?
A: Whenever you are not changing the codec: for example remuxing from one container to another, trimming on keyframe boundaries, or extracting a stream. Copying is fast and lossless. Re-encoding unnecessarily costs time and image quality for no benefit, and it is a very common accidental mistake.

Q: Why did my output lose an audio track or subtitles?
A: Because stream mapping was left to defaults, which select roughly one stream of each type and silently discard the rest. Specify explicitly which streams should appear in the output, and verify by probing the result. In pipelines handling user-supplied media, never rely on default selection.

Q: Why is my processed phone video sideways?
A: Rotation stored in metadata rather than baked into the pixels. Processing that ignores the flag outputs the raw orientation. Processing that applies it twice flips it again. Handle rotation explicitly in your pipeline rather than hoping the defaults do what you expect.

Q: Why does the same command behave differently on another machine?
A: Different FFmpeg version or build. Which encoders are compiled in, which flags exist, and what the defaults are all vary between builds, which is why media toolchains should be pinned in a container and the version recorded on every job.

Translate this page

Machine translation provided by Google Translate, on Google’s servers. We do not check these translations and they will get technical terms wrong. The English page is the authoritative one. Following a link sends this page’s address to Google. Your browser may also offer to translate this page itself, which keeps the request on your device.