FFmpeg OCIO ColorPro
Context: $ARGUMENTS
Quick start
- Check OCIO support: → Step 1
- Apply ACES → Rec.709: → Step 2 + Step 3
- Bake OCIO transform to .cube (fallback when filter missing): → Gotchas +
bake-lut
- Embed ICC profile: → Step 3 (
attach-icc)
When to use
- You want Resolve-grade color fidelity through ffmpeg (ACES, LMTs, film emulation).
- You need to convert between ACES spaces (ACES2065-1, ACEScg, ACEScc, ACEScct) and a display space (Rec.709, sRGB, DCI-P3 D65, Rec.2020 PQ).
- You must embed/detect ICC profiles for downstream compositing/web delivery.
- You want to match a Resolve .cube/OCIO pipeline exactly, without re-grading.
Step 1 — Set OCIO config & check support
ffmpeg -hide_banner -filters | grep -i ocio # expect "ocio" line if built with libOpenColorIO
export OCIO=/path/to/aces_1.3/config.ocio # or ACES 2.0 studio-config
If ocio is missing from the filter list, jump to Gotchas → bake a .cube with ociobake and apply via the lut3d filter (see ffmpeg-lut-grade).
Step 2 — Pick the transform (pinput → poutput)
pinput = source color space. Typical: ACES2065-1 (linear scene-referred, the ACES interchange), ACEScc / ACEScct (log grading spaces), ACEScg (rendering-linear for CGI), camera logs (Input - ARRI - V3 LogC (EI800), Input - Sony - S-Log3, Input - RED - Log3G10).
poutput = target display/encoding. Typical: Output - Rec.709, Output - sRGB, Output - DCI-P3 D65, Output - Rec.2020 ST2084 (HDR10 PQ), Output - Rec.2100 HLG.
Names are config-dependent — run ociocheck --iconfig $OCIO or pyociotools to list exact strings.
Step 3 — Run
# ACES2065-1 → Rec.709 (SDR)
ffmpeg -i in.exr -vf "format=gbrpf32le,ocio=pinput=ACES2065-1:poutput=Output - Rec.709,format=yuv420p" \
-c:v libx264 -crf 16 out.mp4
# ACEScc (log grading) → DCI-P3 D65 (DCDM proxy)
ffmpeg -i graded.mov -vf "format=gbrpf32le,ocio=pinput=ACEScc:poutput=Output - DCI-P3 D65" \
-c:v prores_ks -profile:v 3 out_dcip3.mov
# Apply a CDL / Look (LMT) from config
ffmpeg -i in.mov -vf "ocio=pinput=ACEScc:poutput=Output - Rec.709:look=LMT_Analog_Exposure" out.mov
# Attach ICC profile (bt709 primaries + sRGB TRC) on output
ffmpeg -i in.mov -vf "iccgen=primaries=bt709:trc=iec61966-2-1" -c copy out_icc.mkv
# Detect ICC in source
ffmpeg -i src.mkv -vf "iccdetect=force=1" -f null -
Use scripts/colorpro.py for a guarded wrapper:
uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py check
uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py aces-to-rec709 --input in.exr --output out.mp4
uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py transform --input in.mov --output out.mov \
--pinput "ACEScc" --poutput "Output - Rec.709"
uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py bake-lut --config $OCIO \
--pinput "ACES2065-1" --poutput "Output - Rec.709" --output aces_to_709.cube
uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py attach-icc --input in.mov --output out.mkv \
--primaries bt709 --trc iec61966-2-1
Step 4 — Verify
Gotchas
ocio filter requires ffmpeg built with --enable-libocio. Most Homebrew/apt/chocolatey builds DO NOT include it — ffmpeg -filters | grep ocio tells the truth. If missing: (a) recompile ffmpeg against libOpenColorIO; (b) use ociobake --inputspace X --outputspace Y --format cinespace --lutsize 33 to emit a .cube, then apply with lut3d (see ffmpeg-lut-grade).
$OCIO environment variable must point at a config.ocio file — ffmpeg reads it via getenv("OCIO") unless you pass config=/path.
- ACES config versions differ: 1.0.3 / 1.1 / 1.2 / 1.3 / 2.0 use different role names and transform catalogs. An ACES 1.x pipeline is NOT drop-in compatible with ACES 2.0 (new Output Transforms, different naming). Pin your config version in the project.
- ACES color-space glossary:
ACES2065-1 = AP0 linear, interchange; ACEScg = AP1 linear, rendering/CGI; ACEScc = AP1 log, grading (pure log, no toe); ACEScct = AP1 log with a toe (closer to Cineon feel); ACESproxy = integer-quantized log for 10/12-bit transmission.
- Common ACES roles:
scene_linear, compositing_linear, rendering, aces_interchange, cie_xyz_d65_interchange, color_timing, texture_paint, data, matte_paint.
- poutput spellings are literal & case-sensitive. Examples in ACES 1.3 studio-config:
Output - sRGB, Output - Rec.709, Output - Rec.2020 ST2084, Output - DCI-P3 D65, Output - Rec.2100 HLG.
- ffmpeg's OCIO binding is more limited than Nuke/Resolve/Blender — some LMTs, display-referred looks, and GPU-only transforms may silently fall back or apply incorrectly. For deliveries, verify against a Resolve render.
- OCIO math is floating-point. Always bracket the filter with
format=gbrpf32le on the input side and a target pixel format (e.g. format=yuv420p10le) on the output side to avoid quantization shocks.
- ICC profile embedding in MP4 is limited (iTunes-only
colr atom semantics). Prefer MKV or MOV for ICC-tagged intermediates. iccgen builds an ICC from ffmpeg color tags (primaries, trc); iccdetect reads one from the input stream.
- Rec.709 ≠ sRGB. Same primaries, different transfer functions (Rec.709 is ~gamma 2.4 display; sRGB is piecewise linear + ~2.2). Don't swap them in web delivery.
- For HDR out of OCIO (e.g. ACES → HDR10 PQ BT.2020), set both the transform (
poutput="Output - Rec.2020 ST2084") AND the container color tags (-colorspace bt2020nc -color_primaries bt2020 -color_trc smpte2084) via ffmpeg-hdr-color.
DaVinci Resolve exports .cube LUTs at 17/33/65 cube sizes; reuse via lut3d filter. OCIO look sections map 1:1 to Resolve Color Space Transforms only in v2.0+.
Examples
Example 1: ARRI LogC EXR plates → Rec.709 proxy (VFX review)
ffmpeg -start_number 1001 -i plate_%04d.exr \
-vf "format=gbrpf32le,ocio=pinput=Input - ARRI - V3 LogC (EI800):poutput=Output - Rec.709,format=yuv420p" \
-c:v libx264 -crf 18 -pix_fmt yuv420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 \
review_proxy.mp4
Example 2: Graded ACEScc master → HDR10 deliverable
ffmpeg -i master_ACEScc.mov \
-vf "format=gbrpf32le,ocio=pinput=ACEScc:poutput=Output - Rec.2020 ST2084,format=yuv420p10le" \
-c:v libx265 -x265-params "hdr-opt=1:repeat-headers=1:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:master-display=G(8500,39850)B(6550,2300)R(35400,14600)WP(15635,16450)L(10000000,50):max-cll=1000,400" \
-pix_fmt yuv420p10le hdr10.mp4
Example 3: Film look via OCIO LMT
ffmpeg -i in.mov -vf "ocio=pinput=ACEScct:poutput=Output - Rec.709:look=Kodak_2383_D65" out_kodak2383.mov
Example 4: Fallback — bake .cube, apply with lut3d (no libocio build)
ociobake --iconfig $OCIO --inputspace "ACES2065-1" --outputspace "Output - Rec.709" \
--format cinespace --lutsize 33 > aces_to_709.cube
ffmpeg -i in.exr -vf "format=gbrpf32le,lut3d=aces_to_709.cube,format=yuv420p" out.mp4
Troubleshooting
Error: No such filter: 'ocio'
Cause: ffmpeg built without libOpenColorIO.
Solution: Rebuild with --enable-libocio, or bake the transform with ociobake and apply via lut3d.
Error: Could not find colorspace 'Output - Rec.709'
Cause: The active $OCIO config uses a different name (e.g. ACES 2.0 calls it Rec.709 - Display).
Solution: ociocheck --iconfig $OCIO and list colorspaces; use the exact string.
Output looks washed out / over-saturated
Cause: Double display transform (e.g. applying ACES OutputTransform then also re-tagging as sRGB), or missing format=gbrpf32le stage causing int8 clipping before the transform.
Solution: Ensure a single display transform; always bracket with float format; verify container tags are untouched.
ICC profile not honored in browser
Cause: MP4 container strips/ignores embedded ICC in most players.
Solution: Deliver as MKV/MOV for ICC; for web, rely on colr primaries/trc tags and assume sRGB/Rec.709 rendering intent.
Reference docs
references/ocio.md — ACES config version matrix, role names, colorspace catalog, ociobake usage, vendor log spaces, recipe book.
Related skills
ffmpeg-lut-grade — lut3d/haldclut application, ideal fallback when libocio is absent.
ffmpeg-hdr-color — HDR primaries/TRC tagging, tone-mapping.
ffmpeg-playback — vectorscope/waveform QC.
ffmpeg-probe — verify color tags on output.
ffmpeg-mxf-imf — studio IMF deliverables.
1---2name: ffmpeg-ocio-colorpro3description: Professional color management with ffmpeg + OpenColorIO: ocio filter (OCIO config / LUT / colorspace transforms), ACES workflows (ACEScc / ACEScg / ACES2065-1), Resolve-compatible LUTs, film-emulation via OCIO, iccdetect, iccgen, ICC profile handling. Use when the user asks for ACES color management, apply an OCIO config, use a studio/film LUT, embed ICC profiles, convert between ACEScc/ACEScg/sRGB/Rec709/DCI-P3, do Resolve-grade post color workflows, or match DaVinci Resolve's color pipeline in ffmpeg.4---56# FFmpeg OCIO ColorPro78**Context:** $ARGUMENTS910## Quick start1112- **Check OCIO support:** → Step 113- **Apply ACES → Rec.709:** → Step 2 + Step 314- **Bake OCIO transform to .cube (fallback when filter missing):** → Gotchas + `bake-lut`15- **Embed ICC profile:** → Step 3 (`attach-icc`)1617## When to use1819- You want Resolve-grade color fidelity through ffmpeg (ACES, LMTs, film emulation).20- You need to convert between ACES spaces (ACES2065-1, ACEScg, ACEScc, ACEScct) and a display space (Rec.709, sRGB, DCI-P3 D65, Rec.2020 PQ).21- You must embed/detect ICC profiles for downstream compositing/web delivery.22- You want to match a Resolve .cube/OCIO pipeline exactly, without re-grading.2324## Step 1 — Set OCIO config & check support2526```bash27ffmpeg -hide_banner -filters | grep -i ocio # expect "ocio" line if built with libOpenColorIO28export OCIO=/path/to/aces_1.3/config.ocio # or ACES 2.0 studio-config29```3031If `ocio` is **missing** from the filter list, jump to Gotchas → bake a `.cube` with `ociobake` and apply via the `lut3d` filter (see `ffmpeg-lut-grade`).3233## Step 2 — Pick the transform (pinput → poutput)3435- `pinput` = source color space. Typical: `ACES2065-1` (linear scene-referred, the ACES interchange), `ACEScc` / `ACEScct` (log grading spaces), `ACEScg` (rendering-linear for CGI), camera logs (`Input - ARRI - V3 LogC (EI800)`, `Input - Sony - S-Log3`, `Input - RED - Log3G10`).36- `poutput` = target display/encoding. Typical: `Output - Rec.709`, `Output - sRGB`, `Output - DCI-P3 D65`, `Output - Rec.2020 ST2084` (HDR10 PQ), `Output - Rec.2100 HLG`.3738Names are **config-dependent** — run `ociocheck --iconfig $OCIO` or `pyociotools` to list exact strings.3940## Step 3 — Run4142```bash43# ACES2065-1 → Rec.709 (SDR)44ffmpeg -i in.exr -vf "format=gbrpf32le,ocio=pinput=ACES2065-1:poutput=Output - Rec.709,format=yuv420p" \45 -c:v libx264 -crf 16 out.mp44647# ACEScc (log grading) → DCI-P3 D65 (DCDM proxy)48ffmpeg -i graded.mov -vf "format=gbrpf32le,ocio=pinput=ACEScc:poutput=Output - DCI-P3 D65" \49 -c:v prores_ks -profile:v 3 out_dcip3.mov5051# Apply a CDL / Look (LMT) from config52ffmpeg -i in.mov -vf "ocio=pinput=ACEScc:poutput=Output - Rec.709:look=LMT_Analog_Exposure" out.mov5354# Attach ICC profile (bt709 primaries + sRGB TRC) on output55ffmpeg -i in.mov -vf "iccgen=primaries=bt709:trc=iec61966-2-1" -c copy out_icc.mkv5657# Detect ICC in source58ffmpeg -i src.mkv -vf "iccdetect=force=1" -f null -59```6061Use `scripts/colorpro.py` for a guarded wrapper:6263```bash64uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py check65uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py aces-to-rec709 --input in.exr --output out.mp466uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py transform --input in.mov --output out.mov \67 --pinput "ACEScc" --poutput "Output - Rec.709"68uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py bake-lut --config $OCIO \69 --pinput "ACES2065-1" --poutput "Output - Rec.709" --output aces_to_709.cube70uv run ${CLAUDE_SKILL_DIR}/scripts/colorpro.py attach-icc --input in.mov --output out.mkv \71 --primaries bt709 --trc iec61966-2-172```7374## Step 4 — Verify7576- Visual QC with vectorscope/waveform via `ffmpeg-playback`:77 ```bash78 ffplay -vf "split=2[a][b];[a]vectorscope=m=color3[v];[b]waveform=m=1[w];[v][w]vstack" out.mp479 ```80- Confirm container tags match the transform: primaries, transfer, matrix, range. Use `ffmpeg-probe` → `-show_streams`.81- For HDR/PQ delivery, cross-check with `ffmpeg-hdr-color` (MaxCLL/MaxFALL, mastering display metadata).82- For Netflix/studio IMF, audit with `ffmpeg-mxf-imf`.8384## Gotchas8586- **`ocio` filter requires ffmpeg built with `--enable-libocio`.** Most Homebrew/apt/chocolatey builds DO NOT include it — `ffmpeg -filters | grep ocio` tells the truth. If missing: (a) recompile ffmpeg against libOpenColorIO; (b) use `ociobake --inputspace X --outputspace Y --format cinespace --lutsize 33` to emit a `.cube`, then apply with `lut3d` (see `ffmpeg-lut-grade`).87- `$OCIO` environment variable **must** point at a config.ocio file — ffmpeg reads it via `getenv("OCIO")` unless you pass `config=/path`.88- ACES config versions differ: **1.0.3 / 1.1 / 1.2 / 1.3 / 2.0** use different role names and transform catalogs. An ACES 1.x pipeline is NOT drop-in compatible with ACES 2.0 (new Output Transforms, different naming). Pin your config version in the project.89- ACES color-space glossary: `ACES2065-1` = AP0 linear, interchange; `ACEScg` = AP1 linear, rendering/CGI; `ACEScc` = AP1 log, grading (pure log, no toe); `ACEScct` = AP1 log with a toe (closer to Cineon feel); `ACESproxy` = integer-quantized log for 10/12-bit transmission.90- Common ACES roles: `scene_linear`, `compositing_linear`, `rendering`, `aces_interchange`, `cie_xyz_d65_interchange`, `color_timing`, `texture_paint`, `data`, `matte_paint`.91- poutput spellings are literal & case-sensitive. Examples in ACES 1.3 studio-config: `Output - sRGB`, `Output - Rec.709`, `Output - Rec.2020 ST2084`, `Output - DCI-P3 D65`, `Output - Rec.2100 HLG`.92- ffmpeg's OCIO binding is **more limited than Nuke/Resolve/Blender** — some LMTs, display-referred looks, and GPU-only transforms may silently fall back or apply incorrectly. For deliveries, verify against a Resolve render.93- OCIO math is **floating-point**. Always bracket the filter with `format=gbrpf32le` on the input side and a target pixel format (e.g. `format=yuv420p10le`) on the output side to avoid quantization shocks.94- ICC profile embedding in **MP4 is limited** (iTunes-only `colr` atom semantics). Prefer **MKV or MOV** for ICC-tagged intermediates. `iccgen` builds an ICC from ffmpeg color tags (`primaries`, `trc`); `iccdetect` reads one from the input stream.95- **Rec.709 ≠ sRGB.** Same primaries, different transfer functions (Rec.709 is ~gamma 2.4 display; sRGB is piecewise linear + ~2.2). Don't swap them in web delivery.96- For **HDR out of OCIO** (e.g. ACES → HDR10 PQ BT.2020), set both the transform (`poutput="Output - Rec.2020 ST2084"`) AND the container color tags (`-colorspace bt2020nc -color_primaries bt2020 -color_trc smpte2084`) via `ffmpeg-hdr-color`.97- `DaVinci Resolve` exports .cube LUTs at 17/33/65 cube sizes; reuse via `lut3d` filter. OCIO `look` sections map 1:1 to Resolve Color Space Transforms only in v2.0+.9899## Examples100101### Example 1: ARRI LogC EXR plates → Rec.709 proxy (VFX review)102103```bash104ffmpeg -start_number 1001 -i plate_%04d.exr \105 -vf "format=gbrpf32le,ocio=pinput=Input - ARRI - V3 LogC (EI800):poutput=Output - Rec.709,format=yuv420p" \106 -c:v libx264 -crf 18 -pix_fmt yuv420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 \107 review_proxy.mp4108```109110### Example 2: Graded ACEScc master → HDR10 deliverable111112```bash113ffmpeg -i master_ACEScc.mov \114 -vf "format=gbrpf32le,ocio=pinput=ACEScc:poutput=Output - Rec.2020 ST2084,format=yuv420p10le" \115 -c:v libx265 -x265-params "hdr-opt=1:repeat-headers=1:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:master-display=G(8500,39850)B(6550,2300)R(35400,14600)WP(15635,16450)L(10000000,50):max-cll=1000,400" \116 -pix_fmt yuv420p10le hdr10.mp4117```118119### Example 3: Film look via OCIO LMT120121```bash122ffmpeg -i in.mov -vf "ocio=pinput=ACEScct:poutput=Output - Rec.709:look=Kodak_2383_D65" out_kodak2383.mov123```124125### Example 4: Fallback — bake .cube, apply with lut3d (no libocio build)126127```bash128ociobake --iconfig $OCIO --inputspace "ACES2065-1" --outputspace "Output - Rec.709" \129 --format cinespace --lutsize 33 > aces_to_709.cube130ffmpeg -i in.exr -vf "format=gbrpf32le,lut3d=aces_to_709.cube,format=yuv420p" out.mp4131```132133## Troubleshooting134135### Error: `No such filter: 'ocio'`136137Cause: ffmpeg built without libOpenColorIO.138Solution: Rebuild with `--enable-libocio`, or bake the transform with `ociobake` and apply via `lut3d`.139140### Error: `Could not find colorspace 'Output - Rec.709'`141142Cause: The active `$OCIO` config uses a different name (e.g. ACES 2.0 calls it `Rec.709 - Display`).143Solution: `ociocheck --iconfig $OCIO` and list colorspaces; use the exact string.144145### Output looks washed out / over-saturated146147Cause: Double display transform (e.g. applying ACES OutputTransform then also re-tagging as sRGB), or missing `format=gbrpf32le` stage causing int8 clipping before the transform.148Solution: Ensure a single display transform; always bracket with float format; verify container tags are untouched.149150### ICC profile not honored in browser151152Cause: MP4 container strips/ignores embedded ICC in most players.153Solution: Deliver as MKV/MOV for ICC; for web, rely on `colr` primaries/trc tags and assume sRGB/Rec.709 rendering intent.154155## Reference docs156157- [`references/ocio.md`](references/ocio.md) — ACES config version matrix, role names, colorspace catalog, ociobake usage, vendor log spaces, recipe book.158159## Related skills160161- `ffmpeg-lut-grade` — lut3d/haldclut application, ideal fallback when libocio is absent.162- `ffmpeg-hdr-color` — HDR primaries/TRC tagging, tone-mapping.163- `ffmpeg-playback` — vectorscope/waveform QC.164- `ffmpeg-probe` — verify color tags on output.165- `ffmpeg-mxf-imf` — studio IMF deliverables.