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
eqor 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'. Withfile=C:/LUT/my_lut.cube, FFmpeg stops with aNo option name nearerror. lut3dis 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.