ffmpeg is free and applies a 3D LUT with one filter. What it leaves out is a preview, a per-file amount, and any warning before a bad LUT ruins a batch.
What do you need before you start?
Three things:
- ffmpeg itself. On a Mac,
brew install ffmpeginstalls it through Homebrew, andffmpeg -versionchecks it is there. The commands below are the same on Linux and close on Windows. - A `.cube` file. The
lut3dfilter also reads.3dl,.dat,.m3dand.csp, but.cubeis what camera makers and LUT packs ship. If you are unsure what the file does, what a LUT is covers it in five minutes. - The right LUT for the footage. A conversion LUT built for one log profile will not fix another. For drone footage, the D-Log to Rec.709 guide explains which file matches which camera.
Put the .cube in the same folder as the footage while you work. Paths with spaces or colons need escaping inside a filter, and keeping the LUT next to the clips avoids that entirely.
How do you apply a LUT to one clip?
This is the whole job for a single file:
ffmpeg -i input.mp4 -vf "lut3d=file=look.cube:interp=tetrahedral" \
-c:v libx264 -crf 18 -pix_fmt yuv420p -c:a copy output.mp4What each part does:
lut3d=file=look.cubeapplies the LUT to every frame.interp=tetrahedralsets the interpolation between the LUT's sample points. Tetrahedral is the accurate option, so it is worth writing out.-crf 18keeps the H.264 encode close to the source. Lower is larger and closer.-pix_fmt yuv420pgives a file that plays everywhere.-c:a copycopies the audio track instead of re-encoding it.
On Apple Silicon, -c:v h264_videotoolbox -b:v 40M swaps the software encoder for the Mac's hardware one. It is much faster, and you set a bitrate instead of a quality number.
To see the look before you encode anything, ffplay ships with ffmpeg and takes the same filter: ffplay -vf "lut3d=file=look.cube" input.mp4.
How do you apply a LUT to a whole folder?
A loop. This one grades every .MP4 in the current folder into a graded subfolder and keeps the names:
mkdir -p graded
for f in *.MP4; do
ffmpeg -i "$f" -vf "lut3d=file=look.cube:interp=tetrahedral" \
-c:v libx264 -crf 18 -pix_fmt yuv420p -c:a copy "graded/${f%.*}.mp4"
doneChange *.MP4 to match your camera: *.MOV, *.MTS and so on. The originals are never touched, because ffmpeg writes to a new file.
Two things to know about the loop. It runs one file at a time, and a file that fails prints an error and the loop moves to the next one, so read the output at the end rather than assuming it all went through. And every clip gets exactly the same LUT at exactly the same amount. For a card shot in steady light that is fine. For a shoot where one clip faced the sun, it is the reason people go looking for a strength control.
How do you apply a LUT at less than 100%?
The lut3d filter has no opacity or mix option. To get a partial look you split the video, grade one copy and mix it back over the other:
ffmpeg -i input.mp4 -filter_complex \
"[0:v]format=gbrp,split[base][grade];
[grade]lut3d=file=look.cube[graded];
[graded][base]blend=all_expr='A*0.6+B*0.4',format=yuv420p[v]" \
-map "[v]" -map 0:a? -c:v libx264 -crf 18 -c:a copy output.mp4In the blend expression, A is the first input (the graded copy) and B is the second (the original). A*0.6+B*0.4 is the LUT at 60%. The two numbers must add up to 1. Writing the mix as an expression is longer than using an opacity setting, and the order of the inputs cannot trip you up.
That command is one strength for one clip. Different strengths for different clips means a different command for each one, which is where the loop stops being a loop. Why you would want that, and how the editors handle it, is in LUT intensity explained.
Can ffmpeg apply a LUT to photos?
Yes. The same filter works on stills:
mkdir -p graded
for f in *.jpg; do
ffmpeg -i "$f" -vf "lut3d=file=look.cube" -q:v 2 "graded/$f"
done-q:v 2 sets JPEG quality, where lower numbers are better. PNG and TIFF work the same way with the extension changed. HEIC support depends on how your copy of ffmpeg was built, so test one file first.
The catch is metadata. ffmpeg does not carry the EXIF block across to the JPEG it writes, so capture date, camera, lens and GPS are gone from the output. exiftool -TagsFromFile can copy them back from the originals. Check a portrait frame afterwards, because orientation is the tag that can rotate a picture twice. If photos are most of the job, batch applying a LUT to photos compares the other routes.
What goes wrong most often?
| Symptom | Likely cause | Fix |
|---|---|---|
ffmpeg refuses the .cube | Some DJI LUTs declare their size with a tab where the format expects a space | Replace the tab with a space in a text editor |
| Image far too dark and contrasty | The LUT is for a different log profile | Match the LUT to the exact profile the camera recorded |
| Slight hue shift, skin looks off | ffmpeg converts YUV to RGB and back around lut3d, and untagged files can get the wrong matrix | Set the colour matrix explicitly in a scale filter, or tag the input |
| Output looks washed out next to the editor | The player is reading the file's colour tags differently | Compare in the same player you deliver for |
| Error saying a codec is not supported in the container | The camera recorded PCM audio, which MP4 does not take | Write to .mov, or swap -c:a copy for -c:a aac |
| Loop says "no matches found" | No files with that exact extension | Check the case: .MP4 and .mp4 are different |
The DJI case is the one that wastes most time, because the file is not corrupt and downloading it again changes nothing. The free LUTs guide has more on files that parse strictly in some tools and loosely in others.
When is ffmpeg the wrong tool?
ffmpeg is the right answer when every file wants the same treatment and you are happy to check the results after the run. It gets awkward when you need to judge the look on each file first, when some files need less of it, or when you are handing the job to someone who does not use a terminal.
The editors solve those problems with a timeline, which is a lot of setup for a conversion. Premiere's Interpret Footage route and the LUT workflow in Resolve both work, and both give every clip the same amount unless you adjust them one by one.
BatchTone sits between the two. Its video export runs on ffmpeg, bundled so there is nothing to install, with a preview and a strength dial on top. Drop a folder of photos and clips in, pick the .cube, set a strength on any file that needs less, and export. Below 100% a clip gets the same split, grade and mix as the command above, and photos and clips at the same strength land on the same values. The audio is copied untouched, EXIF from JPEG originals is kept, and malformed DJI .cube files are repaired on the way in. It runs on macOS 12 or later on Apple Silicon, and batch applying a LUT to video shows what it reads and writes. The seven-day trial has everything unlocked.
Common questions
Is ffmpeg's LUT output the same as Resolve's?
Close, when both use tetrahedral interpolation and the same colour space handling. Small differences usually come from how each tool converts between YUV and RGB.
Can ffmpeg apply two LUTs in a row?
Yes. Chain the filters with a comma: lut3d=file=convert.cube,lut3d=file=look.cube. Put the conversion first and the creative look second, the same order you would use in an editor.
Does ffmpeg support 1D LUTs?
Yes, through the separate lut1d filter. Most .cube files sold as looks or conversions are 3D, so lut3d is the one you will use almost every time.
Will grading with ffmpeg lose quality?
The LUT itself costs nothing. The encode does. Export once at a sensible quality setting, and avoid converting, exporting, editing and exporting again.