# How the Color Grading Logic in `helpers/grade.py` Handles Different Color Spaces

> Discover how video-use's helpers grade.py handles color spaces. Learn how it processes normalized pixel values and tone-maps HDR sources for effective color adjustments.

- Repository: [Browser Use/video-use](https://github.com/browser-use/video-use)
- Tags: internals
- Published: 2026-06-30

---

**The color grading logic in [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) operates on normalized pixel values after color space conversion, with HDR sources being tone-mapped to SDR in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) before any grading filters are applied.**

The `browser-use/video-use` repository provides video processing utilities that apply color corrections through a decoupled architecture. The color grading logic itself remains color-space-agnostic by design, processing only normalized luma and saturation values while delegating all color space transformations to upstream components. This separation ensures that the same grading algorithms work identically across SDR (Rec.709) and HDR (Rec.2020/PQ/HLG) sources without requiring color-space-specific branching inside the grading module.

## Two Grading Modes: Preset and Auto

The [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) module implements two distinct approaches for applying color grades to video content.

**Preset mode** applies a static ffmpeg filter chain drawn from predefined constants. These filters—including `eq`, `curves`, and `colorbalance`—are applied unchanged to every frame regardless of content analysis.

**Auto mode** performs data-driven per-clip correction. The `_sample_frame_stats` function analyzes a sample of frames to compute mean brightness, contrast range, and saturation levels. It then constructs a modest `eq=` filter that constrains adjustments to never exceed ±8% on any axis, producing a "clean, not graded" aesthetic that preserves the original character of the footage.

## Bit-Depth Normalization Enables Color-Space Agnosticism

When sampling frames for auto-grading, the code extracts raw Y (luma) and saturation values from ffmpeg’s `signalstats` filter. These values arrive in the native bit depth of the decoded frame—ranging from 0-255 for 8-bit sources to 0-1023 for 10-bit sources.

Rather than assuming a specific color space, the `_sample_frame_stats` function normalizes these values by the maximum possible value for the detected bit depth:

```python

# Lines 115-118 of grade.py

# Normalization ensures downstream math operates in 0..1 

# regardless of source bit depth or color space.

max_val = (2 ** bit_depth) - 1
y_mean = (sum(y_avgs) / len(y_avgs)) / max_val

```

Because this normalization depends solely on bit depth and not on color primaries or transfer functions, the same analysis logic functions correctly for both Rec.709 (SDR) and Rec.2020 (HDR) sources. The grading code never assumes specific color primaries, allowing it to remain agnostic to the underlying color space of the input material.

## HDR Handling Through Tone-Mapping in [`render.py`](https://github.com/browser-use/video-use/blob/main/render.py)

Color space conversion—specifically HDR-to-SDR transformation—occurs entirely outside [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) in the [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) module. This upstream processing ensures that by the time grading filters are applied, the video signal has already been normalized to a standard SDR Rec.709 color space.

The `is_hdr_source` function detects HDR content by checking for PQ (HDR10) or HLG transfer functions via ffprobe:

```python

# Lines 20-30 of render.py

HDR_TRANSFERS = {"smpte2084", "arib-std-b67"}

def is_hdr_source(video: Path) -> bool:
    # ffprobe command queries color_transfer metadata

    return out.stdout.strip() in HDR_TRANSFERS

```

When HDR content is detected, [`render.py`](https://github.com/browser-use/video-use/blob/main/render.py) prepends a comprehensive tone-mapping chain to the ffmpeg filter graph before invoking any grading logic:

```python

# Lines 10-17 of render.py

TONEMAP_CHAIN = (
    "zscale=t=linear:npl=100,"
    "format=gbrpf32le,"
    "zscale=p=bt709,"
    "tonemap=tonemap=hable:desat=0,"
    "zscale=t=bt709:m=bt709:r=tv,"
    "format=yuv420p"
)

```

During segment extraction, the filter assembly follows a strict order that places tone-mapping before grading:

```python

# Lines 79-86 of render.py (extract_segment)

vf_parts: list[str] = []
if is_hdr_source(source):
    vf_parts.append(TONEMAP_CHAIN)   # HDR → SDR conversion first

vf_parts.append(scale)
if grade_filter:
    vf_parts.append(grade_filter)    # Color grade applied to SDR signal

vf = ",".join(vf_parts)

```

This architecture guarantees that the grading logic in [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) always receives an SDR signal, eliminating the need for HDR-specific handling within the grading algorithms themselves.

## What the Grade Filters Actually Modify

The filters generated by [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) operate on the Y′CbCr domain after any tone-mapping has occurred:

- **`eq=`** – Adjusts contrast, brightness (gamma), and saturation using ffmpeg's equalizer filter
- **`curves=`** – Applies a subtle S-curve to the master (luma) channel for refined contrast control
- **`colorbalance=`** – Available only in the optional `warm_cinematic` preset; tweaks shadows and highlights using RGB-style color balance (not used in auto mode)

All adjustments are constrained to modest ranges in auto mode to avoid dramatic color shifts. Since these filters process the signal only after HDR-to-SDR conversion in [`render.py`](https://github.com/browser-use/video-use/blob/main/render.py), they interact with a standardized color space rather than handling the wide gamut and high dynamic range of the original HDR source.

## Practical Usage Examples

Apply auto-grading to an SDR source directly:

```bash
python helpers/grade.py input.mp4 -o out.mp4

```

Process HDR content through the render pipeline (includes automatic tone-mapping):

```bash
python helpers/render.py edl.json -o final.mp4

```

Use an explicit preset instead of auto-analysis:

```bash
python helpers/grade.py input.mp4 -o out.mp4 --preset warm_cinematic

```

Bypass both presets and auto-grade with a custom filter string:

```bash
python helpers/grade.py input.mp4 -o out.mp4 --filter "eq=contrast=1.1:saturation=1.02"

```

## Summary

- **Color-space agnostic design**: [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) normalizes pixel values by bit depth rather than color space, enabling operation across Rec.709 and Rec.2020 sources.
- **Separation of concerns**: HDR detection and tone-mapping reside in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py), which converts all HDR signals to SDR before grading.
- **Normalized processing**: The `_sample_frame_stats` function converts luma values to a 0..1 range based on bit depth, making analysis independent of color primaries.
- **Filter constraints**: Auto mode generates `eq=` filters with adjustments capped at ±8%, operating on Y′CbCr data after tone-mapping.
- **Pipeline order**: The render pipeline assembles filters as tone-map → scale → grade, ensuring consistent SDR processing for all color grades.

## Frequently Asked Questions

### Does [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) support HDR color grading natively?

No, [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) does not process HDR signals directly. According to the source code in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py), HDR sources (PQ or HLG) are first converted to SDR using the `TONEMAP_CHAIN` filter sequence before any grading filters from [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) are applied. The grading logic always operates on tone-mapped SDR content.

### How does the auto-grading algorithm handle different bit depths?

The `_sample_frame_stats` function in [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) detects the bit depth of the source material and normalizes luma values by dividing by `(2 ** bit_depth) - 1`. This produces values in the 0..1 range regardless of whether the source is 8-bit, 10-bit, or higher, allowing the same brightness and contrast calculations to work across different video formats.

### What is the difference between preset mode and auto mode in [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py)?

Preset mode applies static ffmpeg filter strings defined in the `PRESETS` dictionary (such as `warm_cinematic`) unchanged to every frame. Auto mode analyzes sample frames using `signalstats`, calculates mean brightness and saturation, then generates a custom `eq=` filter with corrections limited to ±8% on any axis.

### Why are color space conversions handled in [`render.py`](https://github.com/browser-use/video-use/blob/main/render.py) instead of [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py)?

This architectural decision keeps the grading logic simple and color-space-agnostic. By handling HDR detection and tone-mapping in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py), the system ensures that [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) receives a standardized SDR signal in all cases. This eliminates the need for complex color space branching logic within the grading algorithms and allows the same grading code to work reliably across diverse source materials.