# How to Create Custom FFmpeg Grade Filters for video-use: A Complete Guide

> Learn to create custom FFmpeg grade filters for video-use. This guide explains how to pass filter strings, add presets, and set grades in EDL JSON files for enhanced video editing.

- Repository: [Browser Use/video-use](https://github.com/browser-use/video-use)
- Tags: how-to-guide
- Published: 2026-07-03

---

**You can create custom FFmpeg grade filters in video-use by passing raw filter strings via `--filter`, adding named presets to the `PRESETS` dictionary in [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py), or setting the `"grade"` field in EDL JSON files to any valid FFmpeg filter chain.**

The video-use repository (browser-use/video-use) provides a flexible color grading pipeline that leverages FFmpeg for video processing. While the built-in `auto` grade provides a subtle clean look, you often need custom color corrections, cinematic LUTs, or specific contrast adjustments. This guide explains how to extend the grading system using your own FFmpeg filter chains.

## How the Grade Filter System Works

The colour grading step is implemented in [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py), which defines a dictionary of ready-made presets (`PRESETS`) and the logic that builds filter strings for automatic grading. When the render pipeline runs in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py), each EDL entry's `grade` field is processed by `resolve_grade_filter()` to generate concrete FFmpeg filter arguments.

The `resolve_grade_filter()` function accepts four input types:

- **`None`** or empty string: No grading applied (video is copied)
- **`"auto"`**: Triggers per-segment auto-grading via `auto_grade_for_clip()`
- **Preset name**: Looks up the filter in the `PRESETS` dictionary
- **Raw filter string**: Any string containing `=` or `,` is treated as a literal FFmpeg filter

## Three Methods to Create Custom FFmpeg Grade Filters

### Method 1: Pass Raw Filters via Command Line

The simplest way to apply a custom grade is using the `--filter` flag when running [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py). This bypasses preset lookup and applies the filter verbatim to every segment.

```bash
python helpers/grade.py input.mp4 -o output.mp4 \
  --filter "eq=contrast=1.12:brightness=-0.03:saturation=0.95,curves=master='0/0 0.3/0.25 0.7/0.75 1/1'"

```

The `--filter` flag overrides any preset configuration and passes the exact FFmpeg filter chain to the processing pipeline.

### Method 2: Add a Named Preset to grade.py

For reusable color grades, extend the `PRESETS` dictionary in [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py). After adding your preset, reference it by name in CLI commands or EDL files.

Edit [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) and append to the `PRESETS` dictionary:

```python
PRESETS["my_cinematic"] = (
    "eq=contrast=1.15:brightness=-0.02:saturation=0.90,"
    "colorbalance=rs=0.03:gs=0.00:bs=-0.04:"
    "rm=0.05:gm=0.02:bm=-0.01:"
    "rh=0.07:gh=0.02:bh=-0.05,"
    "curves=master='0/0 0.2/0.18 0.8/0.85 1/1'"
)

```

Now invoke the preset via command line:

```bash
python helpers/grade.py input.mp4 -o output.mp4 --preset my_cinematic

```

The render script resolves the preset name to the filter string via `get_preset()`.

### Method 3: Define Custom Filters in EDL Files

For per-segment control, edit your EDL JSON file directly. Set the `"grade"` field to either a preset name or a raw filter string.

```json
{
  "grade": "eq=contrast=1.2:saturation=0.95",
  "ranges": [
    {"source": "take1", "start": 0, "end": 12},
    {"source": "take2", "start": 5, "end": 18}
  ],
  "sources": {
    "take1": "videos/take1.mp4",
    "take2": "videos/take2.mp4"
  }
}

```

When [`render.py`](https://github.com/browser-use/video-use/blob/main/render.py) processes this file, `resolve_grade_filter()` detects the `=` character and treats the value as a raw FFmpeg filter, passing it unchanged to the per-segment `extract_segment()` call.

## Technical Implementation Details

The `auto_grade_for_clip()` function produces a subtle "clean-look" filter that never applies creative color shifts. If you need specific aesthetics like teal-orange looks, vintage LUTs, or custom contrast curves, you must supply the full filter chain yourself or extend the preset table.

When `apply_grade()` processes a segment, it automatically adds `-c:v libx264 -pix_fmt yuv420p` to the FFmpeg command when a filter is present. This ensures compatibility with the concatenation pipeline that stitches per-segment video files together.

## Compatibility and Pipeline Considerations

Because the render pipeline concatenates per-segment video with `-vf <filter>`, any custom filter you provide must remain compatible with the rest of the pipeline. Specifically:

- **Pixel format**: Avoid changing pixel formats unless you also adjust the output options in `apply_grade()`
- **Filter syntax**: Ensure your filter string uses valid FFmpeg syntax with proper escaping for quotes and special characters
- **Performance**: Complex multi-stage filters increase processing time proportionally

## Summary

- **Three input methods**: Use `--filter` for one-off grades, edit `PRESETS` in [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) for reusable looks, or set the `"grade"` field in EDL JSON for per-segment control
- **Resolution logic**: `resolve_grade_filter()` in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) automatically detects raw filters (containing `=` or `,`) versus preset names
- **Codec handling**: The `apply_grade()` function automatically appends `-c:v libx264 -pix_fmt yuv420p` when filters are active
- **Auto limitations**: The built-in `auto_grade_for_clip()` provides only subtle corrections; creative grades require custom filter chains

## Frequently Asked Questions

### What is the difference between "auto" and custom presets?

The `"auto"` value triggers `auto_grade_for_clip()`, which calculates per-clip brightness, contrast, and saturation adjustments to create a neutral, clean look. Custom presets in `PRESETS` allow you to define specific aesthetic filter chains (like cinematic color grades or stylized LUTs) that are applied uniformly without automatic calculation.

### Can I combine multiple FFmpeg filters in one grade?

Yes. You can chain multiple FFmpeg filters using commas within your filter string. For example: `"eq=contrast=1.15:brightness=-0.02,curves=master='0/0 0.2/0.18 1/1',colorbalance=rs=0.03"` combines equality adjustments, curves, and color balance in a single grade pipeline.

### Why does my custom filter fail during rendering?

Custom filters usually fail for two reasons: syntax errors in the FFmpeg filter string, or pixel format incompatibilities. Ensure your filter string contains valid FFmpeg syntax (check for proper quote escaping), and verify that your filter does not change the pixel format unexpectedly. The pipeline expects `yuv420p` output for concatenation compatibility.

### Where should I store custom presets for team sharing?

Store custom presets directly in the `PRESETS` dictionary within [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) and commit the changes to your repository fork. This ensures that all team members using the same codebase have access to consistent grade presets. Alternatively, document your filter strings in your project's EDL templates for per-project customization.