• Terms of Service

Learn Internet Grow

Tech Skills

  • Learn
  • Books
  • Search
  • Create
  • Measure
  • Secure
  • Share
  • Terms of Service

FFmpeg Filter Graphs Are a Tiny Programming Language

Filed Under: Create

Most people meet FFmpeg filter graphs the same way: they paste a command off a forum, it works, and they have no idea why. Then they change one small thing — swap a comma for a semicolon, add a second input file — and it fails with an error that reads like a compiler complaint. That reaction is exactly right, because it is a compiler complaint. The string you pass to -filter_complex is not a flag with a fussy value. It is a program, written in a small dataflow language, describing a directed acyclic graph — a DAG — of processing nodes.

Once you see the graph, the syntax stops being arbitrary and starts being obvious. This piece builds that view from the simplest possible case up to a real multi-input picture-in-picture composite, and along the way explains why filtering and -c copy can never appear on the same stream.

FFmpeg filter graphs — a single command-line string unfolding into a directed graph of processing nodes

Everything below describes the CLI grammar as documented for FFmpeg 8.1 “Hoare,” released 2026-03-16 (the release notes and current source are on ffmpeg.org’s download page). Filter names, options, and defaults do change between releases — check ffmpeg -h filter=NAME on your own build rather than trusting any article, including this one, as a permanent reference.

Where Filters Sit in FFmpeg’s Pipeline

FFmpeg processes media in five stages, and knowing which stage filters occupy explains most of their behaviour. Demux splits a container file — an MP4 or MKV, which is a wrapper holding streams, not a compression format — into individual compressed streams. Decode turns compressed packets into raw frames. Filter transforms those raw frames. Encode compresses them again. Mux writes the results back into a container.

Inside the C libraries this split has two names: an AVPacket is a chunk of still-compressed data, and an AVFrame is a decoded picture or block of audio samples. The libavcodec encode/decode documentation describes the whole API as a packets-in/frames-out and frames-in/packets-out contract.

Filters operate on decoded frames in a lower layer while compressed packets travel a separate sealed layer above

libavfilter — the library behind every filter name in this article — lives strictly in frame-land. It has no idea what H.264 or AV1 is. It sees arrays of pixels and arrays of audio samples, and nothing else. That single fact is the layering motif this whole subject hangs on, and it will explain a lot in a moment.

Start Small: -vf and a Single Chain

The simplest useful filter command has one input, one filter, one output:

ffmpeg -i in.mp4 -vf scale=1280:-2 out.mp4

-vf is shorthand for -filter:v, and -af is the audio equivalent. Both are described in the ffmpeg CLI reference as simple filtergraphs: exactly one input and one output, both the same media type, applied to one stream. That covers a huge share of real work.

The scale arguments are worth decoding, because they show the language already doing something clever. 1280 is the target width. -2 tells scale to compute the height that preserves the source aspect ratio, then round it to a multiple of two. The rounding is not cosmetic: most video is stored with chroma subsampling, meaning colour information is sampled at lower resolution than brightness — the common yuv420p layout stores one colour sample per 2×2 block of pixels — so odd dimensions have nowhere to put half a colour sample and many encoders simply refuse. Use -1 when you want the exact aspect-preserving value and know your dimensions are already even.

Chain a second filter with a comma:

ffmpeg -i in.mp4 -vf scale=1280:-2,fps=30 out.mp4

That is a two-node graph: scale feeds fps. Nothing else changed.

Comma Versus Semicolon: The Most-Confused Syntax in FFmpeg

Here is the single piece of filter syntax that trips up more people than everything else combined, and it takes one sentence to fix.

A comma connects two filters into one chain. A semicolon ends a chain and starts a new, independent one inside the same graph. The filters documentation states this directly, and it is the entire difference between a pipeline and a graph.

A comma joins two filters into one continuous chain while a semicolon leaves two separate, unconnected chains

Compare:

crop=640:480,vflip           <- ONE chain: crop feeds vflipcrop=640:480;vflip           <- TWO chains, unconnected: an error waiting to happen

The first is a pipeline. The second declares two separate chains that share a graph but have no wiring between them, and FFmpeg will complain that pads are dangling. A semicolon is not a stronger comma. It is a statement terminator. You reach for it the moment your graph stops being a straight line — when one stream needs to become two, or two need to merge into one — and you use labels to reconnect the pieces you just separated.

That is the whole reveal, stated plainly: a comma is sequential composition, a semicolon is a new branch, and labels are the wires. Everything else is filter names.

Named Pads Are the Wires Between Nodes

A pad is a filter’s input or output socket. A label in square brackets names one so another chain can attach to it:

  • [0:v] means “the video stream of input file 0” — the standard stream specifier, counting files from zero.
  • [1:a] means “the audio stream of input file 1.”
  • [tmp], [pip], [v] are names you invent for intermediate results.
  • A label written on the left of a filter is an input it reads; on the right, an output it writes.

Two rules make labels behave predictably. First, position in the string is irrelevant — the graph is defined by which labels match, not by what order you typed the chains in. Second, each label is written exactly once and read exactly once. Trying to consume [tmp] twice is an error; that is precisely what split exists for. If the first filter’s input or the last filter’s output is unlabelled, the defaults in and out are assumed.

There is one CLI-level consequence worth memorising: unlabelled outputs of a complex graph go into the output file automatically, but the moment you name an output you must map it explicitly with -map "[v]". Forgetting that produces an output file mysteriously missing a stream.

Named pads act as wires — labels match by name, not by their position in the filter string

The syntax, in one table

TokenNameWhat it meansExample
,chain separatorConnect these two filters in sequence; the left one’s output feeds the right one’s inputscale=1280:-2,fps=30
;chain terminatorEnd this chain, start a new independent one in the same graph[0:v]split[a][b];[a]hflip[out]
[name]pad labelName a link; on the left it is an input, on the right an output[0:v]scale=640:-2[small]
[N:v] / [N:a]stream specifierThe video/audio stream of input file N, counted from 0[1:a]volume=0.5[quiet]
=argument list startEverything after it configures the filtercrop=640:480
:argument separatorSeparate positional or key=value argumentscrop=w=640:h=480:x=0:y=0
\escapeProtect a character the parser would otherwise eat (: , ; [ ])drawtext=text=a\\:b
filter@idinstance nameGive one filter instance a name so commands can be sent to it at runtimeoverlay@pip
sws_flags=…;graph-level prefixSet the scaler flags used by filters auto-inserted into this graphsws_flags=bicubic;[0:v]…

Arithmetic inside arguments is real too: iw and ih are the input width and height, W/H and w/h refer to main and overlay dimensions inside overlay, and the full expression grammar — min(), if(), t for timestamp — is documented in the FFmpeg utilities reference. crop=iw/2:ih:0:0 means “the left half, whatever the source resolution is,” which is a small program, not a constant.

split Fans Out, overlay Composites

A graph needs two primitives beyond a straight line: something to duplicate a stream and something to merge streams back together.

split is the fan-out. split=2[a][b] produces two identical copies of its input on independently named pads; asplit does the same for audio. overlay is the merge — it takes a main input and a secondary input and composites the second on top of the first at coordinates you supply.

The filters documentation’s own canonical example uses both, and it is worth reading node by node because it is the whole language in one line:

split[main][tmp]; [tmp]crop=iw:ih/2:0:0,vflip[flip]; [main][flip]overlay=0:H/2
            ┌──────────────┐  input ───►│    split     │            └──┬────────┬──┘        [main] │        │ [tmp]               │        ▼               │   ┌─────────┐   ┌───────┐               │   │  crop   │──►│ vflip │               │   └─────────┘   └───┬───┘               │                     │ [flip]               ▼                     ▼            ┌───────────────────────────┐            │         overlay           │──► output            └───────────────────────────┘

Three chains, separated by semicolons. Chain one splits the input into [main] and [tmp]. Chain two crops [tmp] to its top half and flips it vertically, using a comma because those two run in sequence. Chain three takes both [main] and [flip] and composites the mirrored half onto the bottom of the original. It looks like a mirrored reflection effect; structurally it is a diamond-shaped DAG, which is exactly the kind of shape graph theory exists to describe.

A split fans one stream into two branches that are processed separately and recombined by an overlay node

overlay has one sharp edge worth stating up front: both inputs must already agree on pixel format and be sized sensibly relative to each other. Scale before you overlay, never after. FFmpeg will auto-insert conversion filters where it can, but relying on that is how you end up with surprising output and surprising cost.

Building a Picture-in-Picture Graph, Step by Step

Picture-in-picture is the canonical case that cannot be written with -vf at all, because -vf is defined as one input and one output. Two input files means a complex graph. This is the shape behind almost every screen recording with a webcam corner you have ever watched.

Step 1 — get the inputs. Two -i flags give you [0:v], [0:a], [1:v], [1:a].

Step 2 — shrink the inset. The webcam is full-size and needs to become a thumbnail:

[1:v]scale=480:-2[pip]

Step 3 — composite. Place [pip] in the bottom-right corner with a 24-pixel margin, using overlay‘s built-in dimension variables so the maths works at any resolution:

[0:v][pip]overlay=W-w-24:H-h-24[v]

Step 4 — mix the audio. Both sources have sound, so merge them with amix:

[0:a][1:a]amix=inputs=2:duration=first[a]

Joined with semicolons, that is the finished program:

ffmpeg -i screen.mp4 -i cam.mp4 \  -filter_complex "[1:v]scale=480:-2[pip];[0:v][pip]overlay=W-w-24:H-h-24[v];[0:a][1:a]amix=inputs=2:duration=first[a]" \  -map "[v]" -map "[a]" -c:v libx264 -crf 20 -c:a aac out.mp4
  screen.mp4 ─[0:v]────────────────┐                                   ▼   cam.mp4  ─[1:v]──► scale ─[pip]─► overlay ─[v]──► libx264 ──┐                                                               ├─► out.mp4  screen.mp4 ─[0:a]──┐                                         │                     ├──────────► amix ─[a]────────► aac ──────┘   cam.mp4  ─[1:a]──┘

Four nodes, six edges, one line of shell. Note duration=first, which ends the mix when the first input ends rather than the default longest — a small decision that determines whether your recording stops with the screen capture or runs on with a black frame while the webcam keeps talking. And note that because [v] and [a] are named, both need -map.

A picture-in-picture filter graph with a scaled inset composited over a main video and two audio streams mixed in a separate subgraph

The same fan-out shape scales up: one source, split into several branches, each branch scaled and encoded at a different size, is exactly how an adaptive-streaming ladder is built. It is the identical graph pattern, just wider.

concat: One Name, Three Different Tools

Joining two files end to end is where FFmpeg’s naming becomes genuinely hostile, because there are three mechanisms called concat and they solve different problems at different layers. Conflating them is the most common way a “simple” join goes wrong.

MechanismLayerWhat it needsRe-encodes?Typical use
concat filter (-filter_complex "…concat=n=2:v=1:a=1…")Decoded framesSegments must match on resolution; the filter negotiates a common pixel format and audio format, but resolution differences you must convert yourselfYes — it is a filter, so frames are decoded and re-encodedJoining clips that differ in codec, or joining as part of a larger graph
concat demuxer (-f concat -safe 0 -i list.txt)Container/packetA text file of file 'clip.mp4' lines; all files must share codec and stream parametersNo — works with -c copyFast, lossless joining of clips from the same source or encoder
concat protocol (concat:a.ts|b.ts)Raw bytesA format that is genuinely concatenable at byte level, such as MPEG-TS or DVNoReassembling byte-splittable streams

The rule of thumb: if the files came out of the same camera or the same encode settings, the demuxer joins them at roughly disk speed with no generation loss. If they differ in resolution, frame rate, or codec, you need the filter, and you are re-encoding whether you like it or not. The demuxer is documented with the other container options in the formats reference; the protocol lives in the protocols reference.

The concat demuxer joins sealed packets untouched while the concat filter decodes, processes, and re-encodes the frames

The Everyday Nodes: scale, crop, and fps

Three filters cover most real work, and each has a behaviour worth knowing rather than guessing.

scale=W:H resamples the picture. Beyond -1/-2 it takes an explicit algorithm — bilinear, bicubic, lanczos — and the choice matters more when downscaling aggressively than when nudging a size.

crop=w:h:x:y cuts a rectangle, with x/y defaulting to centred if omitted. Because arguments are expressions, crop=iw-200:ih:100:0 trims 100 pixels from each side regardless of source width.

fps=30 forces a constant frame rate by dropping or duplicating frames. That is a blunt instrument and it is the correct instrument surprisingly often, because many downstream tools assume constant frame rate. What it is not is a smooth motion-interpolating conversion; nothing is invented, frames are only repeated or discarded.

Timestamps are the invisible half of this. setpts (and asetpts for audio) rewrites presentation timestamps directly, and setpts=PTS-STARTPTS is the standard repair after trim, which otherwise leaves the first output frame carrying its original offset. Since multi-input filters such as overlay, concat, and amix all assume their inputs are synchronised, an unreset PTS after a cut is the most common cause of audio/video drift in a hand-built graph — invisible in the syntax, visible only in the output.

Audio Filters Speak the Same Grammar

There is no separate audio language. amix, aresample, asplit, atrim, volume, and aformat sit in the same graph, connected by the same commas, semicolons, and labels. The a prefix is a convention marking the audio counterpart of a video filter, not a different dialect.

amix=inputs=N sums several audio streams into one, with duration choosing whether the result runs to the longest input, the shortest, or the first. aresample wraps libswresample to convert sample rate, sample format, and channel layout — and FFmpeg inserts it for you automatically whenever two audio pads disagree, which is why it shows up in verbose logs you did not ask for.

The important structural point: audio and video are separate subgraphs inside one -filter_complex string. They do not connect to each other; they are simply two DAGs declared together and muxed into one output at the end. Looking at the picture-in-picture example again, the overlay branch and the amix branch never touch.

Filter Order Changes Both Quality and Speed

Because a graph is a program, the order of its nodes has a cost, not just a meaning.

Run a filter before scale and it works on full-resolution pixels: expensive, but with every bit of original detail available. Run it after scale and it works on fewer, already-averaged pixels: cheap, with less to work with. Neither is universally right. Detail-sensitive work — denoising, sharpening, precise cropping — usually belongs before the downscale. Cosmetic work that only needs to look correct at final size belongs after it.

A filter placed before a downscale processes far more pixels than the same filter placed after it

The overlay case is concrete. Compositing a logo onto a 4K frame and then scaling to 1080p means overlay touches roughly four times as many pixels as scaling first and compositing onto the smaller frame — and it also means the logo gets resampled along with everything else, which may or may not be what you want for a crisp graphic. Same two filters, same output size, materially different cost and appearance depending on which side of the comma each one sits.

The same trade-off recurs one level up, which is really a bandwidth decision: scaling down before encoding permanently discards detail in exchange for fewer bits per frame, while encoding at native resolution ships pixels many viewers will just downscale away on their own screens. That is the question an adaptive-bitrate ladder answers separately for every rung.

If a graph is your bottleneck rather than the encoder, -filter_threads and -filter_complex_threads control how many threads libavfilter uses. And on a GPU path, the filters themselves must be GPU-resident — scale_cuda, scale_vaapi, scale_qsv — or every frame gets copied back to system memory and returned, which erases most of the acceleration. Mixing CPU and GPU filters in one graph silently inserts those transfers.

Why a Filter and -c copy Cannot Coexist

Now the layering motif pays off.

-c copy performs a remux: FFmpeg reads compressed packets from the demuxer and hands them straight to the muxer without ever invoking a decoder. Nothing is decompressed, nothing is recompressed, there is no generation loss, and it runs at roughly disk speed. Changing an MP4 into an MKV without touching the video is the classic case.

libavfilter operates on decoded frames. It cannot see a packet; it has no concept of one.

Those two statements are incompatible by construction. To filter a stream you must decode it, and once you have decoded it you must re-encode it to write it out. So the moment a filter touches a stream, stream copy for that stream becomes impossible — and FFmpeg does not silently pick one, it refuses the command outright and tells you that filtering and stream copy cannot be used together.

The escape hatch is that this is per stream. If your graph only touches video, the audio is still free:

ffmpeg -i in.mp4 -filter_complex "[0:v]scale=1280:-2[v]" -map "[v]" -map 0:a -c:a copy out.mp4

Video is re-encoded because it was filtered; audio is copied bit for bit because it never entered the graph. That is not a special case — it is the direct consequence of filters living in frame-land while copy lives in packet-land, and it is worth internalising because it predicts the behaviour of every filter you will ever add.

Common One-Liners and the Graphs They Build

One-linerGraph it buildsWorth knowing
-vf scale=1280:-2Single node, one in, one out-2 preserves aspect ratio and rounds to an even number for chroma subsampling
-vf crop=iw/2:ih:0:0,scale=640:-2Two nodes in sequence (comma)Crop first, on full-resolution pixels, then scale the smaller result
-vf fps=30,format=yuv420pRate conversion feeding a pixel-format conversionformat=yuv420p is the usual fix for output that plays nowhere
-filter_complex "[0:v][1:v]hstack"Two inputs merging into one nodeBoth inputs must be the same height
-filter_complex "[0:v]split[a][b];[a]hflip[l];[b]negate[r];[l][r]hstack[v]" -map "[v]"Diamond DAG: split, two parallel branches, mergeThe split/merge pattern in miniature; named output needs -map
-filter_complex "[1:v]scale=480:-2[p];[0:v][p]overlay=W-w-24:H-h-24[v]" -map "[v]" -map 0:a -c:a copyPicture-in-picture, audio untouchedVideo re-encodes, audio copies — the layering rule in one command
-f concat -safe 0 -i list.txt -c copyNo graph at all — a demuxer, not a filterLossless join, but only for files with identical codec parameters

Debugging a Graph That Won’t Build

Filter-graph errors are parse and link errors, and they behave like them. A checklist that resolves most of them, roughly in order of how often it is the answer:

Debugging a Graph That Won't Build — a painterly illustration conveying this idea from "FFmpeg Filter Graphs Are a Tiny Programming Language"
  1. Count your labels. Every label must be written by exactly one pad and read by exactly one pad. An unread label leaves a dangling output; a label read twice needs a split.
  2. Check comma versus semicolon. A semicolon where you meant a comma disconnects a pipeline you thought was joined. This is the number one cause.
  3. -map your named outputs. If you labelled the final output, nothing reaches the file until you map it.
  4. Use -filter_complex, not -vf, for anything with two inputs. -vf is defined as one-in-one-out; a second input file cannot be expressed in it. (-lavfi is an accepted alias for -filter_complex.)
  5. Scale before merging. overlay, hstack, vstack, and the concat filter all have dimension or format expectations. Meet them explicitly instead of hoping auto-inserted conversions do what you meant.
  6. Ask the binary what a filter actually accepts. ffmpeg -filters lists every filter compiled into your build with its input/output signature; ffmpeg -h filter=overlay prints one filter’s full option list and pad layout. This is faster and more reliable than a search engine, because it describes your build.
  7. Turn the log up. -v verbose or -loglevel debug prints the graph as it was actually configured, including every conversion filter FFmpeg inserted on your behalf. Those auto-inserted scale, format, and aresample nodes are frequently the explanation for both unexpected output and unexpected slowness.
  8. Get long graphs out of the shell. ffmpeg can read an option’s value from a file using the -/option form — -/filter_complex graph.txt — which keeps a large graph in version control and out of shell-quoting hell. FFmpeg’s source tree also ships a small graph2dot utility under tools/ that renders a filtergraph description as a Graphviz diagram, which is the fastest way to check that the graph in your head matches the graph in the string.

The Transferable Idea

The reason this is worth learning even if you never composite a webcam again is the design pattern underneath it.

FFmpeg had a problem: expose an arbitrarily complex, user-defined processing pipeline through a command-line interface. The obvious solutions are both bad — dozens of special-case flags, or a configuration file format nobody wants to learn. Instead the project embedded a minimal dataflow language in a single string. Sequential composition is a comma. Parallel composition is a semicolon plus labels. Nodes are named functions with keyword arguments. That is nearly the smallest grammar that can express a DAG, and it fits in a flag.

Reading -filter_complex as source code rather than as configuration is the whole shift — the same shift that makes any unfamiliar syntax legible once you stop memorising incantations and start reading it as code. The next time you paste a filter string off a forum, trace the labels. You will find a graph, and you will be able to change it.

And once the graph is legible, the harder questions open up honestly: whether a given effect is better done as a filter or in a dedicated tool, whether a chroma-key composite belongs before or after your scale, and where in the chain you are paying for pixels you are about to throw away.

❓ Frequently Asked Questions

What is the difference between -vf and -filter_complex in FFmpeg filter graphs? -vf (an alias for -filter:v) declares a simple filtergraph: exactly one input and one output of the same media type, applied to a single stream. -filter_complex is required the moment a graph has multiple inputs, multiple outputs, or explicit stream references. Picture-in-picture, side-by-side stacking, and the concat filter all need -filter_complex; a straight scale or crop does not.

When do I use a comma and when do I use a semicolon? A comma chains filters in sequence — the left filter’s output feeds the right filter’s input. A semicolon ends one chain and begins a new, independent chain inside the same graph, which you then reconnect using [labels]. If your processing is a straight line, you only need commas. Semicolons appear exactly when the graph branches or merges.

Why does FFmpeg say filtering and stream copy cannot be used together? Because libavfilter works on decoded frames while -c copy moves still-compressed packets without decoding them at all. Filtering a stream forces a decode, and a decode forces a re-encode on output. This applies per stream, so you can filter video while copying audio in the same command — the streams that never enter the graph can still be copied.

Should I use the concat filter or the concat demuxer? Use the demuxer (-f concat -safe 0 -i list.txt) when the files share the same codec and stream parameters — it joins at the packet level, works with -c copy, and involves no quality loss. Use the concat filter when the segments differ in resolution, frame rate, or codec, or when the join is part of a larger graph; it operates on decoded frames and therefore re-encodes.

Does the order of filters change the output? Yes, in two ways. It changes the result — cropping then scaling is not the same operation as scaling then cropping — and it changes the cost, because a filter placed before a downscale processes far more pixels than the same filter placed after it. Detail-sensitive work generally belongs before a downscale; work that only needs to be correct at final size belongs after.

How do I debug a filter graph that will not build? Check that every label is written by exactly one pad and read by exactly one pad, then check your commas against your semicolons, then confirm that named outputs are passed to -map. After that, run with -v verbose to see the graph as FFmpeg actually configured it — including the conversion filters it inserted on your behalf — and use ffmpeg -h filter=NAME to confirm what a filter really accepts on your specific build.

📖 Further Reading

  • FFmpeg’s own filters documentation — the authoritative reference for filtergraph syntax and every filter’s options, regenerated from source.
  • The ffmpeg CLI reference for -vf, -filter_complex, -map, and -c copy.
  • The libavfilter overview for the library behind the syntax.
  • Leandro Moreira’s free Digital Video Introduction — the best free grounding in what a frame, a codec, and a container actually are before you start filtering them.

📚 Recommended Resources

Disclosure: some links below are Amazon affiliate links. If you buy through them, I earn a small commission — at no extra cost to you — which helps keep this site running.

Start with the free material, and mean it. Filter graphs are one of the few technical subjects where the authoritative reference is free, current, and better than anything in print — the filter list is regenerated from FFmpeg’s own source on every build, and no book can track it. Work through these first; buy a book only for the theory underneath the filters, which is where paper still wins.

  • The FFmpeg filters documentation — the authority for everything in this article: the comma/semicolon grammar, pad labels, and every filter’s full option list. It is generated from the source tree, so it describes the filters as they actually are rather than as they were when someone wrote a tutorial. Pair it with ffmpeg -h filter=NAME on your own build, which is the only reference guaranteed to match the binary you are running.
  • The ffmpeg CLI reference and ffmpeg-all — the definitions of simple versus complex filtergraph, -map, -c copy, and the -/option file form that gets a long graph out of shell-quoting hell. ffmpeg-all is every option, muxer, demuxer, and filter on one nightly-regenerated page.
  • Graphviz — free, and the natural companion to the graph2dot utility in FFmpeg’s own tools/ directory. Render your filtergraph string as an actual picture and you can see at a glance whether the graph in your head matches the graph in your string. For a language whose failure mode is a mis-wired label, that is the single highest-value tool on this list.
  • Digital Video Introduction — Leandro Moreira’s free, illustrated primer on what a frame, a codec, and a container actually are. Read it if the frames-versus-packets distinction in this article felt like the interesting part rather than the obvious part; it is the best free grounding in the layer that filters sit on top of.
  • A Digital Media Primer for Geeks — Xiph.org’s free video primer, and the clearest explanation anywhere of sampling, quantization, and chroma subsampling. This is the background for why scale=1280:-2 rounds to an even number instead of just doing the arithmetic.

Digital Video and HD: Algorithms and Interfaces

Charles Poynton · 2nd edition, Morgan Kaufmann, 2012 · Hardcover, 752 pp

The book for the layer libavfilter actually operates on: pixels, colour spaces, gamma, and chroma subsampling. Almost every sharp edge in this article traces back to something Poynton explains properly — why -2 exists in a scale argument, why yuv420p is the pixel format that makes a stubborn file play everywhere, and why overlay insists its two inputs agree on pixel format before it will composite them. If you want to know what a scaling algorithm is really doing when you choose bicubic over lanczos, this is where that knowledge lives. Worth knowing what it is not: a codec book. Roughly one part of six covers compression, and it has no HEVC or AV1 content at all — buy it to understand pixels, not encoders.


Introduction to Data Compression

Khalid Sayood · 5th edition, Morgan Kaufmann, 2017 · Paperback, 790 pp

This article’s last section says that touching a stream with a filter costs you a re-encode, and that stream copy is free and bit-exact. Sayood is the book that turns those two sentences into an understanding: entropy coding, quantization, and transform coding from first principles, which is to say a proper account of what you are paying for every time a frame goes back through an encoder. It is general compression theory rather than a video-specific text, and it is the better book for it — the ideas transfer to audio, images, and everything else you will ever compress. Read it if you have started asking how much a filter costs rather than whether it costs.


Video Demystified: A Handbook for the Digital Engineer

Keith Jack · 5th edition, Newnes, 2007 · Paperback, 944 pp

The practical engineer’s handbook for the operations this article treats as single nodes: scaling, cropping, frame-rate conversion, colour-space conversion, and the interface conventions around them. It is genuinely useful on why fps=30 drops and duplicates frames rather than inventing them, and on what a format conversion actually involves — the kind of detail that explains those auto-inserted scale, format, and aresample nodes you find in a verbose log. One honest caveat: this edition is from 2007, so it predates HEVC, AV1, and most of the modern codec landscape entirely. That matters less here than it would elsewhere, because signal processing on decoded frames has not changed much — but do not buy it as a codec reference.

Newsletter

Tags

Agentic AI Agentic Development AI AI Agents AI Coding Agents Artificial intelligence Backlinks Blockchain Bounded Context Browser ChatGPT Plugins Claude Code CLPR Context Engineering Cross-Chain Cryptocurrency Developer Tools Distributed Systems DLT Domain-Driven Design Email Google Search HBAR Hedera Hedera Hashgraph Internet LLM Machine Learning MCP Microservices Model Context Protocol News NLP Orchestration Photoshop Productivity Saga Pattern SEO Smart Contracts Software Architecture software engineering Twitter Typescript Web3 Writing

Recent Articles

  • QUIC and HTTP/3: The Protocol That Won Is Being Routed Around October 11, 2026
  • The Mark I Perceptron: The Machine That Learned to See October 11, 2026
  • GraphRAG vs Vector RAG: Knowledge Graphs for AI, Explained October 11, 2026
  • Cardano Tokenomics: How ADA’s Supply Actually Works October 10, 2026
  • Transformer Encoder vs Decoder: How to Read the Diagram October 10, 2026
  • Nobody Checks the Verified Badge: The Real State of Git Commit Signing October 10, 2026

Copyright © 2026 · News Pro Theme On Genesis Framework · WordPress · Log in

Content on this site is produced with the assistance of Large Language Models and edited by a human. It is intended for educational purposes — facts may occasionally be incorrect. If you spot an error, please email Larionovdmitri@learninternetgrow.com.

Affiliate Disclosure: This site contains affiliate links to Amazon and other partners. If you purchase through these links, LearnInternetGrow.com may earn a commission at no additional cost to you. We only recommend products we genuinely believe in.

Privacy Policy · Terms of Service