What Is a 3D LUT?

A 3D LUT (3-Dimensional Look-Up Table) is a table that maps each input color (a combination of R, G and B) to an output color. Once a look such as a “film look” is saved as a LUT file, loading that file applies the same color change to any video. In FFmpeg, the lut3d filter loads the file. To build a look by hand instead, start with the hue filter.

Basic Commands

Apply a .cube LUT

ffmpeg -i input.mp4 -vf "lut3d=file=my_lut.cube" -pix_fmt yuv420p output.mp4

Put the path to the LUT file after file=. lut3d works in RGB, so without -pix_fmt yuv420p the H.264 output is 4:4:4, which many players and devices cannot play.

When the Path Contains Spaces

ffmpeg -i input.mp4 -vf "lut3d=file='my lut file.cube'" -pix_fmt yuv420p output.mp4

If a file or folder name contains spaces, wrap the path in single quotes (').

Supported Formats

Extension Format Name
.cube Adobe/DaVinci Resolve CUBE (most common)
.3dl Autodesk 3DL
.dat DaVinci dat
.m3d Pandora m3d
.csp cineSpace csp

.cube is the most widely used format. Choose it when you can.

How to Reduce LUT Intensity

lut3d has no option for the strength of the effect. If a LUT is too strong, use one of these:

  • Add eq or the curves filter after the LUT to pull saturation and contrast back.
  • Mix (blend) the graded video with the original in a filtergraph.

The first is simpler. Here eq after the LUT lowers contrast and saturation a little:

ffmpeg -i input.mp4 \
  -vf "lut3d=file=my_lut.cube,eq=contrast=0.9:saturation=0.8" -pix_fmt yuv420p \
  output.mp4

A Concrete .cube File Structure (Reference)

# Minimal 2x2x2 CUBE file (for testing)
LUT_3D_SIZE 2
0.0 0.0 0.0
1.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 0.0
0.0 0.0 1.0
1.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0

Lines that start with # are comments. LUT_3D_SIZE sets how many levels each of R, G and B has; here it is two. Each line after it is the output color (in R G B order) for one input color. This example returns every color unchanged, so applying it does not change the picture.

LUTs made for real work are much finer, for example 33×33×33 (35,937 entries) or 65×65×65.

Create and Apply a Minimal .cube File for Testing

This saves the same LUT as test.cube and applies it to a video.

cat > test.cube << 'EOF'
LUT_3D_SIZE 2
0.0 0.0 0.0
1.0 0.0 0.0
0.0 1.0 0.0
1.0 1.0 0.0
0.0 0.0 1.0
1.0 0.0 1.0
0.0 1.0 1.0
1.0 1.0 1.0
EOF

ffmpeg -i input.mp4 -vf "lut3d=file=test.cube" -pix_fmt yuv420p output.mp4

This LUT does not change any colors. If FFmpeg writes the output file without an error, the LUT file was read correctly.

The lines from cat > test.cube << 'EOF' to EOF work in a Mac or Linux terminal. In Windows Command Prompt or PowerShell, create test.cube with the same content in a text editor such as Notepad, then run only the last ffmpeg line.

Applying a LUT with the haldclut Filter (An Alternative Method)

haldclut takes the LUT as an image (PNG or TIFF) in the Hald CLUT format. Give the video as the first input and the LUT image as the second.

ffmpeg -i input.mp4 -i lut_image.png \
  -filter_complex "[0][1]haldclut" -pix_fmt yuv420p \
  output.mp4

Color Grading Workflow

1. Converting from Log Footage (S-Log3 → Rec.709)

ffmpeg -i slog3_footage.mp4 \
  -vf "lut3d=file=SLog3_To_Rec709.cube" -pix_fmt yuv420p \
  output.mp4

Log footage, such as Sony’s S-Log3, looks flat and washed out as recorded. To bring it back to a normal look (Rec.709), you need the conversion LUT made for that log format.

2. Applying a Film Look

ffmpeg -i input.mp4 \
  -vf "lut3d=file=film_look.cube" -pix_fmt yuv420p \
  output.mp4

Many sites offer free LUTs, IWLTBAP among them.

Combining lut3d with curves and eq

Add eq or another filter after the LUT to fine-tune the result. This example raises contrast and saturation slightly.

ffmpeg -i input.mp4 \
  -vf "lut3d=file=my_lut.cube,eq=contrast=1.05:saturation=1.1" -pix_fmt yuv420p \
  output.mp4

Notes

  • Use an absolute path for the LUT file. A relative path is resolved from the folder you run FFmpeg in. On Windows, write the drive colon as \: and use forward slashes: lut3d=file='C\:/LUT/my_lut.cube'. With file=C:/LUT/my_lut.cube, FFmpeg stops with a No option name near error.
  • lut3d is part of libavfilter, so you need no extra library.
  • If the LUT file is written incorrectly (for example, its comment lines), FFmpeg stops with an error.
  • On high-resolution footage, applying a LUT can run slower than real time.

Measured: time and size

This is the command that was measured.

ffmpeg -i input.mp4 -vf lut3d=file=look.cube,eq=saturation=0.95:contrast=0.98 -c:v libx264 -crf 23 -preset medium -an output.mp4
Metric Measured
Wall-clock time 53.26 s (2.25x real time)
Output size 84.21 MB
Size vs. the no-filter baseline -2.4%

Rig: a 1920x1080, 30 fps, 120 s, 351.4 MB H.264 clip (Big Buck Bunny, looped, CC BY 3.0) on a Core i9-14900KF with 32 threads, FFmpeg 8.1 (gyan.dev), 2026-09-05, one command at a time. The raw numbers are in the dataset. The command uses -an, so the time covers the video only. look.cube is an identity 33x33x33 LUT: it returns every colour unchanged.

Why the number lands where it does

For comparison, the same clip was re-encoded with no filter:

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -an output.mp4

That took 27.97 s. With lut3d and eq it took 53.26 s, almost twice as long. That is 2.25x real time on a 32-thread i9.

Other operations from the same run: a colorbalance + eq correction chain took 53.29 s, about the same as lut3d’s 53.26 s. curves took 42.01 s, boxblur 59.37 s, and a downscale to 720p 18.43 s. Changing every pixel is not what makes it slow: eq (27.07 s) and hue (27.06 s) also rewrite every pixel and were no slower than the 27.97 s baseline. lut3d, colorbalance and curves work in RGB, so every frame is converted from YUV to RGB and back. The measured command also has no -pix_fmt, so x264 encoded 4:4:4 (yuv444p), which has twice as many values per frame as the baseline’s 4:2:0. That larger encode is a large part of the extra time. The rest is the RGB conversion and the filters themselves.

The biggest gap is against -c copy (copying without re-encoding). In the same run, operations that did not re-encode the video finished in about a second: stream mapping took 0.41 s, moving the moov atom 0.49 s, and an audio fade that copies the video and re-encodes only the audio 1.03 s. Re-encoding the video took 27.97 s with no filter and 729.16 s for a VP9 transcode. A LUT rewrites pixel values, so it cannot use -c copy and always needs a re-encode. Try looks on a 10-second excerpt, and run the full video once when you are happy with the result.

The file was 84.21 MB, 2.4% smaller than the baseline. look.cube leaves colours unchanged, so the LUT did not cause this. It comes almost entirely from eq=saturation=0.95:contrast=0.98, which lowers saturation and contrast slightly. A LUT that changes colours will change the size too. If size matters, encode a 10-second excerpt with the LUT you plan to use and compare.

When you compare before and after, check skin, white walls and sky gradients first. A strong LUT can look good at first glance but shift skin tones or cause banding in a sky. If it does, pull it back with a filter after the LUT, such as eq=saturation=0.9.

For log footage, apply the manufacturer’s conversion LUT first and the look LUT after it. The result is much more predictable in that order.

  • haldclut — apply an image-format LUT
  • curves — color correction with tone curves
  • hue — adjust hue, saturation, and brightness
  • eq — adjust brightness, contrast, and gamma