Tags: music
Apply a refined cinematic / Fuji-inspired color grade to video files using presets or custom parameters.
Requirements
- Python 3.11+ (uses built-in
tomllib; tested on 3.14) - FFmpeg (required for H.264 encoding + audio
preservation)
- macOS:
brew install ffmpeg - Linux:
apt install ffmpegorpacman -S ffmpeg - Windows: ffmpeg.org
- macOS:
Features
- Preset system β Save and reuse grading
configurations in
presets.toml - Soft film-like contrast (S-curve)
- Natural warmth with selective cool-tone desaturation
- Elliptical vignette
- Optional real
.cube3D LUT support - Automatic H.264 encoding + audio preservation (via FFmpeg)
- Simple CLI with short alias support
Install
cd cinematic-grade
uv syncOptional short alias (add to ~/.zshrc or
~/.bashrc):
alias cg='cinematic-grade'
# or if you prefer staying with uv run:
# alias cg='uv run cinematic-grade'Quick Start
List Available Presets
cg --list-presets
# or
uv run cinematic-grade --list-presetsUse a Preset
# Default preset is "subtle" (soft lifestyle look)
cg input.mp4 output.mp4
# Explicit presets
cg input.mp4 output.mp4 --preset subtle
cg input.mp4 output.mp4 --preset warm
cg input.mp4 output.mp4 --preset chris
cg input.mp4 output.mp4 --preset strong
cg input.mp4 output.mp4 --preset movieOverride Preset Parameters
# Use warm preset but boost saturation
cg input.mp4 output.mp4 --preset warm --saturation 1.20
# Use strong preset but reduce LUT strength
cg input.mp4 output.mp4 --preset strong --lut-strength 0.50Presets
Presets are defined in presets.toml.
Main LUT: classic_chrome_sRGB.cube
(Fujifilm Classic Chrome)
Default preset: subtle
| Preset | Description | LUT Strength | Best for |
|---|---|---|---|
subtle |
Soft lifestyle look (default) | 0.40 | Everyday / vlog / travel |
warm |
Warm cinematic | 0.50 | Balanced cinematic look |
chris |
Warm intimate guitar / cozy room | 0.40 | Intimate music / soft daylight |
strong |
Stronger film look | 0.70 | More stylized shots |
punchy |
Higher contrast + saturation | 0.55 | Vibrant / energetic clips |
soft |
Very soft and low contrast | 0.35 | Dreamy / gentle look |
movie |
Heavy cinematic (Movie Film LUT) | 0.80 | Dramatic / heavy grade |
pure |
Warm grade only (no LUT) | β | Clean warmth, no film LUT |
neutral |
No grading | β | Comparison / baseline |
Recommended Starting Points
# Soft lifestyle / travel footage (default)
cg input.mp4 output.mp4
# Warm intimate (guitar, cozy rooms, soft light)
cg input.mp4 output.mp4 --preset chris
# General cinematic
cg input.mp4 output.mp4 --preset warm
# Heavier film look
cg input.mp4 output.mp4 --preset strongUsage Without / Beyond Presets
# Full custom grade
cg input.mp4 output.mp4 \
--lut classic_chrome_sRGB.cube \
--lut-strength 0.45 \
--warmth 1.13 \
--saturation 1.12 \
--contrast 1.06 \
--black-lift 0.07 \
--vignette 0.09 \
--cool-desat 0.08Parameter Reference
| Parameter | Range | Default | Purpose |
|---|---|---|---|
--preset |
name | subtle | Load a preset from presets.toml |
--lut |
file path | (none) | Path to .cube 3D LUT |
--lut-strength |
0.0β1.0 | 0.75 | How strongly to apply the LUT |
--warmth |
0.8β1.3 | 1.12 | Shift toward warm (orange/red) tones |
--saturation |
0.8β1.5 | 1.15 | Color intensity multiplier |
--contrast |
0.9β1.2 | 1.08 | Overall punch and separation |
--black-lift |
0.0β0.15 | 0.06 | Shadow lift (softer blacks) |
--vignette |
0.0β0.3 | 0.12 | Edge darkening strength |
--cool-desat |
0.0β0.3 | 0.18 | Desaturate cool tones (blues/cyans) |
--no-normalize |
flag | off | Skip loudness normalization entirely |
--loudness-target |
LUFS | -12.0 | Target integrated loudness |
--true-peak |
dBTP | -1.5 | Max true peak (clipping ceiling) |
--loudness-range |
LU | 11.0 | Allowed loudness range (dynamics) |
--audio-bitrate |
e.g.Β 256k | 256k | AAC bitrate for the final export |
Processing Order
- LUT (base look)
- Black lift
- Contrast (S-curve)
- Warmth
- Saturation + cool desaturation
- Vignette
The numeric controls (warmth, contrast, etc.) fine-tune the result after the LUT is applied.
Audio Normalization
Every export runs a two-pass loudnorm by default:
- Measure β ffmpeg analyzes the source audioβs integrated loudness, true peak, and loudness range (EBU R128).
- Apply β those measured values are fed back in with
linear=true, producing a single flat gain adjustment rather than a dynamic, sample-by-sample one.
This brings clips to a consistent, comparable loudness across a project without pumping/ducking during quiet vs.Β loud passages β the recorded dynamics and tonal character are preserved, just leveled.
# Default: normalize to -12 LUFS, 256k AAC
cg guitar_take.mp4 output.mp4 --preset chris
# Louder target, tighter true-peak ceiling
cg guitar_take.mp4 output.mp4 --preset chris --loudness-target -14 --true-peak -1.0
# Skip normalization entirely, keep original levels
cg guitar_take.mp4 output.mp4 --preset chris --no-normalizeNote: two-pass loudnorm only stays
purely linear if the correction needed is modest. If a source clip is
unusually quiet or has very large dynamic swings, ffmpeg may fall back
to its dynamic algorithm for that clip even with
linear=true on. Thereβs no clean way to detect this from
the exit code β if a specific clipβs audio sounds off after
normalization, this is the first thing to check.
LUTs
| File | Character | Recommended use |
|---|---|---|
classic_chrome_sRGB.cube |
Muted, clean, documentary | Main / default LUT |
eterna_sRGB.cube |
Soft, low-contrast film | Soft cinematic |
provia_sRGB.cube |
More vivid + contrasty | Punchier looks |
movie_film.cube |
Heavy cinematic / stylized | Dramatic grades |
Additional Fuji LUTs:
https://github.com/abpy/FujifilmCameraProfiles/tree/master/cube%20lut
(use the *_sRGB.cube versions)
Creating Custom Presets
Edit presets.toml and add a new section:
[presets.my_look]
description = "Custom grade"
lut = "classic_chrome_sRGB.cube"
lut_strength = 0.55
warmth = 1.13
saturation = 1.15
contrast = 1.09
black_lift = 0.05
vignette = 0.10
cool_desat = 0.10Then use it:
cg input.mp4 output.mp4 --preset my_lookBatch Processing
#!/bin/bash
PRESET=${1:-subtle}
OUTPUT_DIR=${2:-./graded}
mkdir -p "$OUTPUT_DIR"
for video in *.mp4; do
echo "Grading: $video"
cg "$video" "$OUTPUT_DIR/${video%.mp4}_graded.mp4" --preset "$PRESET"
donechmod +x batch_grade.sh
./batch_grade.sh # uses default (subtle)
./batch_grade.sh chris # warm intimate
./batch_grade.sh warm # warmer cinematic
./batch_grade.sh strong # stronger film lookTechnical Notes
- Output format: Final file is H.264 + AAC with
+faststart(immediately playable in most players and IDEs). - Audio preservation: Original audio is muxed automatically when FFmpeg is available.
- Intermediate: A temporary silent video is created during processing and deleted afterwards.
- Quality: Default CRF 18 (good balance of quality vs file size). Lower CRF = higher quality / larger files.
Troubleshooting
Presets not loading
- Make sure
presets.tomlis in the project root - Run
cg --list-presetsto verify
Audio missing / unplayable file
- Install FFmpeg (
brew install ffmpegon macOS)
LUT not found
- Check that the
.cubefile exists in the project root - Verify the filename in
presets.toml
File Structure
cinematic-grade/
βββ pyproject.toml
βββ presets.toml
βββ classic_chrome_sRGB.cube # Main LUT
βββ eterna_sRGB.cube
βββ provia_sRGB.cube
βββ movie_film.cube
βββ README.md
βββ cinematic_grade/
βββ __init__.py
βββ cli.py
βββ grade.py