Colour correction before and after. Left is the original; right adds red and removes blue in the shadows with colorbalance, warming the dark areas

Left is the original, right is colorbalance=rs=0.3:bs=-0.3,eq=saturation=1.15. rs (red) and bs (blue) change only the shadows, so dark areas such as the jackets and trousers turn reddish and the bright areas barely change.

Source: Tears of Steel (CC) Blender Foundation | mango.blender.org, CC BY 3.0. One frame of the film, processed with the filter shown.

eq — Basic Brightness, Contrast, and Saturation Controls

eq is the simplest color filter. For each setting in more detail, see brightness, contrast and saturation with eq.

ffmpeg -i input.mp4 -vf "eq=brightness=0.1:contrast=1.2:saturation=1.3:gamma=1.0" output.mp4
Parameter Range Default Description
brightness -1.0 to 1.0 0 Brightness (0 = no change)
contrast -1000 to 1000 1 Contrast (1 = no change)
saturation 0 to 3.0 1 Saturation (1 = no change, 0 = grayscale)
gamma 0.1 to 10 1 Gamma correction (1 = no change)

Common Adjustments

Brighten slightly and boost contrast:

ffmpeg -i input.mp4 -vf "eq=brightness=0.05:contrast=1.3" output.mp4

Convert to grayscale:

ffmpeg -i input.mp4 -vf "eq=saturation=0" output.mp4

curves — Fine-Grained Tone Curves

curves changes each input value to an output value along a curve that you set with points. For more on points and per-channel curves, see tone curves with the curves filter. An S-shaped curve raises contrast:

ffmpeg -i input.mp4 -vf "curves=all='0/0 0.25/0.15 0.5/0.5 0.75/0.85 1/1'" -pix_fmt yuv420p output.mp4

Each point is written input/output, with values from 0 to 1. all applies the curve to every color channel.

curves works on RGB pictures. Without -pix_fmt yuv420p, FFmpeg encodes the result as 4:4:4 H.264 (High 4:4:4 Predictive), which many phones, TVs and browsers cannot play. Add -pix_fmt yuv420p whenever you use curves, colorbalance or lut3d.

Per-Channel Adjustments

Lift the red channel for a warmer look:

ffmpeg -i input.mp4 -vf "curves=red='0/0 0.5/0.6 1/1'" -pix_fmt yuv420p output.mp4

Lower the blue channel to reduce cool tones:

ffmpeg -i input.mp4 -vf "curves=blue='0/0 0.5/0.4 1/0.9'" -pix_fmt yuv420p output.mp4

Using Presets

ffmpeg -i input.mp4 -vf "curves=preset=vintage" -pix_fmt yuv420p output.mp4

The presets are none, color_negative, cross_process, darker, increase_contrast, lighter, linear_contrast, medium_contrast, negative, strong_contrast, vintage.

colorbalance — Tint Shadows / Midtones / Highlights

Shift the shadows cooler (more blue) and warm the highlights slightly:

ffmpeg -i input.mp4 -vf "colorbalance=bs=0.1:rs=-0.1:rh=0.1" -pix_fmt yuv420p output.mp4
Parameter Description
rs/gs/bs Red/green/blue shift in the shadows (-1 to 1)
rm/gm/bm Red/green/blue shift in the midtones
rh/gh/bh Red/green/blue shift in the highlights

Combining Filters

Put several filters in one -vf, separated by commas. They run in order, here eq first and then colorbalance:

ffmpeg -i input.mp4 -vf "eq=brightness=0.05:contrast=1.1,colorbalance=rs=-0.05:bs=0.05" -pix_fmt yuv420p output.mp4

Which Filter to Use

Goal Recommended filter
Quickly tune brightness, contrast, or saturation eq
Shape tone with curves curves
Independently color shadows, midtones, and highlights colorbalance
Apply a .cube LUT lut3d

Measured: time and size

The measured command:

ffmpeg -i input.mp4 -vf colorbalance=rs=0.15:bs=-0.15,eq=saturation=1.1 -c:v libx264 -crf 23 -preset medium -an output.mp4
Measured
Wall-clock time 53.29 s (2.25x realtime)
Output size 87.71 MB
Size vs. an unfiltered re-encode +1.7%

Test machine: Core i9-14900KF (32 threads), FFmpeg 8.1 (gyan.dev build). Source: 1920x1080, 30 fps, 120 s, 351.4 MB (Big Buck Bunny, looped, CC BY 3.0). Run on 2026-09-05, one command at a time. The comparison is the same source re-encoded with no filter: 27.97 s, 86.26 MB. The raw numbers are in the dataset.

Reading the numbers

The time depends on the filter. With no filter, the re-encode took 27.97 s. eq and hue took about the same (27.07 s and 27.06 s), curves 42.01 s, this colorbalance + eq chain 53.29 s, and boxblur 59.37 s. The curves and colorbalance runs had no -pix_fmt yuv420p, so they were encoded as 4:4:4, which is slower than 4:2:0; part of their extra time comes from that. For this chain, 120 s of video took 53.29 s: about 0.44 times the clip length, or 2.25x realtime.

The file size barely changed: +1.7%. Other colour filters can change it more. In the same run, all at the same CRF (quality setting), curves with a contrast boost came out +13.4%, hue +3.1%, and lut3d + eq with slightly lower saturation and contrast -2.4%. Raising contrast or saturation made the file larger; lowering them made it smaller. These changes range from a few percent to the low teens. If you need a smaller file, change the bitrate or the resolution, not the colour: scaling down to 720p in the same run gave -52.3%.

Colour correction cannot use -c copy. In the same run, jobs that did not re-encode the video took 0.41 s (stream mapping), 0.49 s (moving the moov atom, the MP4 index) and 1.03 s (an audio fade that copies the video and re-encodes only the audio). Colour filters change the pixels, so the video has to be re-encoded, which took 53.29 s here. Do cuts, joins and container changes separately with -c copy, and put all colour filters into one re-encode so that you pay the 53.29 s only once.

Frequently Asked Questions

Should I use eq or curves for colour correction?

Use eq to change the brightness, contrast or saturation of the whole picture. Use curves to shape the shadows, midtones and highlights separately. curves is closer to a professional grading workflow.

What is the difference between gamma and brightness?

Brightness adds the same amount to every value. Gamma bends the curve instead: above 1 it brightens the midtones, and below 1 it darkens them. Gamma usually looks more natural. It keeps only the values 0 and 255 fixed. Most video uses 16 for black and 235 for white, and gamma moves those too: gamma=2 turns 16 into 64.

Why does the corrected video look greenish?

Usually the green curve or the saturation was pushed too far. Reset that channel. Make changes to all channels together first, and adjust single channels last.

Can I apply a 3D LUT instead?

Yes. lut3d reads .cube files: ffmpeg -i in.mp4 -vf lut3d=look.cube -pix_fmt yuv420p out.mp4. A LUT (lookup table) is the standard way to share a graded look.

Does this preserve HDR colour?

Not as they are. eq converts a 10-bit HDR source to 8-bit (yuv420p) before it works. curves keeps 10 bits, but both filters treat the HDR (PQ or HLG) values as plain numbers. To keep HDR, grade in software that handles PQ and HLG, such as DaVinci Resolve. If SDR output is fine, convert to SDR first and then use these filters.