DEV Community

Cover image for The ffmpeg Pipeline Nobody Explains
Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on AI-assisted

The ffmpeg Pipeline Nobody Explains

Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. Star us to help devs discover the project, give it a try, and share your feedback to help improve the product.

ffmpeg is the one CLI tool everyone uses and most doesn't understand

Every developer has run this exact incantation at some point in their life, copy-pasted from a Stack Overflow answer from 2014, with zero idea what it does:

ffmpeg -i input.mov -vcodec h264 -acodec mp2 output.mp4
Enter fullscreen mode Exit fullscreen mode

It works. You move on. You never ask what -vcodec actually does, or why some conversions finish in a blink and others chew your CPU for ten minutes.

Turns out there's a genuinely elegant pipeline hiding under the hood, and once you see it, half of ffmpeg's flag soup starts making sense on its own.

ffmpeg was created by Fabrice Bellard back in the year 2000, and the name is a mashup of "fast forward" and MPEG, the Moving Picture Experts Group, the folks behind most of the video formats you've heard of.

It's not just a CLI toy either.

It's the encode/decode engine quietly running inside Chrome, Blender, YouTube, Vimeo, and roughly half of every video-adjacent tool you've ever used.
Source: ffmpeg.org.

The pipeline nobody tells you about

Here's the thing that clicked for me: ffmpeg isn't one operation, it's a pipeline, and every flag you pass just tweaks one stage of it.

ffmpeg pipeline: input to demux to decode to filter to encode to mux to output

Walk through it left to right:

  1. Input — your file lands, in.mp4, whatever.
  2. Demux — a demultiplexer splits the container into its separate streams. Your "video file" was never one thing, it's a video track, an audio track, maybe subtitles, all interleaved into one file for convenience.
  3. Decode — each stream's compressed packets get decoded into raw, uncompressed frames. This is the CPU-hungry part.
  4. Filteroptional. This is the only stage that actually touches pixels or audio samples: brightness, contrast, scaling, adding subtitles, drawing a waveform. Skip it and nothing changes.
  5. Encode — raw frames get compressed back down into packets, in whatever codec you asked for.
  6. Mux — a multiplexer interleaves the encoded streams back into one output container.
  7. Outputout.mkv, done.

ffmpeg -i in.mp4 out.mp4 runs the whole thing with sane defaults. ffprobe just runs the first couple of stages and prints what it finds, no encode required, which is why it's instant.

With over 100 codecs supported, that same seven-stop pipeline is how ffmpeg decodes, encodes, transcodes, filters, and plays basically any multimedia file that exists. Source: ffmpeg.org/about.html.

The flag that changes everything: -c copy

Once you see the pipeline, one thing jumps out immediately: decode and encode are the only expensive stages. Demux and mux are just bookkeeping, shuffling bytes around.

So what happens if you skip decode and encode entirely?

ffmpeg -i input.mp4 -c copy output.mkv
Enter fullscreen mode Exit fullscreen mode

-c copy skips decode and encode entirely, vs -c:v libx264 which runs the full pipeline

-c copy tells ffmpeg "don't touch the streams, just repackage them." It demuxes, then immediately muxes into the new container.

No decode, no encode, no quality loss, because the actual video bytes never change, they just get moved into a different box.

This is why converting .mp4 to .mkv is instant, but converting .mp4 to .webm is not: mp4 and mkv can both hold H.264 video, so it's a pure repackage.

webm wants VP8/VP9/AV1, a codec mp4 usually isn't carrying, so ffmpeg has no choice but to actually decode and re-encode every frame.

Once you've internalized that, most of ffmpeg's common recipes stop being magic incantations and start being obvious:

ffmpeg -i input.mov -c copy -ss 00:00:30 -t 00:00:10 clip.mov
Enter fullscreen mode Exit fullscreen mode

Trim ten seconds starting at 0:30. Since we're not changing codecs, -c copy keeps it instant. Same file, smaller slice.

Need to glue several clips together? List them in a text file and hand it to the concat demuxer:

ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
Enter fullscreen mode Exit fullscreen mode

Still -c copy. Still no re-encode. It's the same pipeline principle, just applied to multiple inputs instead of one.

When you actually need the expensive path

Sometimes copy isn't an option, because you're deliberately changing the pixels or the codec, not just the wrapper. That's when -vf (video filter) and real encode flags come in:

ffmpeg -i input.mp4 -vf "scale=1280:720" -r 30 -b:v 2M -c:v libx264 output.mp4
Enter fullscreen mode Exit fullscreen mode

-vf scale resizes, -r sets the frame rate, -b:v sets the video bitrate, -c:v libx264 picks the encoder. Every one of these forces a real decode → filter → encode pass, which is exactly why this version is slow and -c copy isn't.

Subtitles go through the same filter stage. Got an .srt file? Convert it to .ass first, then burn it in with -vf subtitles, since -vf is the only stage in the whole pipeline that's allowed to touch a frame.

Here's the decision tree I actually keep in my head now, roughly:

flowchart TD
    A[I have a media file and ffmpeg] --> B{Just changing container, same codecs?}
    B -->|yes| B1[-c copy]
    B -->|no| C{Need a smaller file or a different codec?}
    C -->|yes| C1[-c:v libx264 -c:a aac]
    C -->|no| D{Just trimming a section?}
    D -->|yes| D1[-ss start -t dur -c copy]
    D -->|no| E{Joining multiple clips together?}
    E -->|yes| E1[concat demuxer + -c copy]
    E -->|no| F{Changing resolution, framerate or bitrate?}
    F -->|yes| F1[-vf scale, -r, -b:v]
    F -->|no| G{Burning in subtitles?}
    G -->|yes| G1[srt to ass, then -vf subtitles]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start    fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef action    fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a

    class A start
    class B,C,D,E,F,G decision
    class B1,C1,D1,E1,F1,G1 action

Every branch of that tree is the same seven-stage pipeline, just with a different subset of stages actually doing work.

Gru's Plan meme about -c copy being the twist that actually works in your favor

The rest of the toolbox

Two more binaries ship alongside ffmpeg and are worth knowing exist:

  • ffprobe — inspects a file and dumps its metadata: codecs, resolution, duration, bitrate, stream count. It's the tool for "wait, what actually is this file" before you commit to a slow encode.
  • ffplay — a minimal media player built on the same libraries, for when you just want to preview something from the terminal without opening a full video app.

And under all three sits a stack of libraries, libavcodec, libavformat, libavfilter, and friends, that other software links against directly rather than shelling out to the CLI. That's the actual reason ffmpeg ended up powering Chrome's media playback and Blender's video editor: it was never really "a command line tool," it was a media engine that happened to ship a command line tool as its front door.

This Is Fine meme about starting a real libx264 re-encode and watching your CPU catch fire

Why this is worth knowing

None of this is trivia for its own sake.
Once the pipeline is in your head, ffmpeg's entire flag surface stops being a wall of options to memorize.

You start asking "which stage am I actually touching" before you type a command, and the answer tells you whether it'll take a second or a coffee break, and whether you even need to.

That's the whole trick.
The tool has a hundred flags, but only one shape underneath all of them.



Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.

I'm building LiveReview, a blast-radius aware AI code review built for your business-critical systems.

Instead of presenting every diff with equal emphasis, LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.

Spend code review effort where business risk is highest — not spread evenly across every diff.

⭐ Star it on GitHub:

GitHub logo HexmosTech / LiveReview

Blast-Radius Aware AI Code Review for Business-Critical Systems

LiveReview

gitleaks.yml osv-scanner.yml govulncheck.yml semgrep.yml dependabot-enabled mcp-testcases.yml

LiveReview: Blast-Radius Aware AI Code Review for Business-Critical Systems

LiveReview is an AI code reviewer that scores every hunk of a diff by blast radius: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.

blast-radius-demo.mp4

LiveReview's Blast Radius & Review Priority scoring, live in the diff viewer.
















The exact math, not a black box Visualize blast radius at a glance Every factor that feeds the score

How does Blast Radius scoring work? (a more technical explanation)

Here's the goal:

  • A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.
  • A 300-line UI change in one file, fully covered by…




Click below to try LiveReview with your codebase:

LiveReview Banner

Top comments (0)