# How to Integrate Custom FFmpeg Filters into the Video‑Use Rendering Pipeline

> Learn to integrate custom FFmpeg filters into video-use rendering. Supply raw filter strings via EDL grade or grade.py CLI for seamless injection into video filter chains.

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

---

**You can integrate custom FFmpeg filters into video-use by supplying a raw filter string through the EDL `grade` field or the [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py) CLI, which [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) injects into the per-segment video filter chain during extraction.**

The `browser-use/video-use` repository assembles FFmpeg command-lines dynamically to process video segments. Understanding exactly where and how the pipeline accepts custom video filters allows you to apply color grading, transformations, or any valid FFmpeg filter without breaking the built-in HDR tone-mapping or scaling logic.

## Where Filter Injection Happens in the Pipeline

The core rendering logic lives in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py), specifically within the `extract_segment` function. This function constructs the `-vf` (video filter) argument for FFmpeg by concatenating filter strings into a comma-separated list.

According to the source code, the pipeline builds the filter graph in this exact order:

```python

# helpers/render.py → extract_segment

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

vf_parts.append(scale)                  # portrait/landscape scaling

if grade_filter:                       # ← your custom filter lands here

    vf_parts.append(grade_filter)
vf = ",".join(vf_parts)

```

The `grade_filter` string is resolved earlier by `resolve_grade_filter` in the same file. This function interprets the EDL's `grade` value and returns either a preset from [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py), an auto-grade sentinel (`__AUTO__`), or your raw FFmpeg filter string.

## Methods to Supply Custom FFmpeg Filters

You have two primary entry points to inject custom filters: directly in the EDL JSON structure or via the command-line interface using [`grade.py`](https://github.com/browser-use/video-use/blob/main/grade.py).

### Via the EDL JSON Grade Field

The simplest method is adding a top-level `"grade"` key to your EDL (Edit Decision List) JSON file. The value can be any valid FFmpeg video filter string.

```json
{
  "grade": "eq=contrast=1.2:saturation=1.1",
  "ranges": [
    {"source": "intro", "start": 0, "end": 5},
    {"source": "main", "start": 10, "end": 20}
  ],
  "sources": {
    "intro": "raw/intro.mov",
    "main": "raw/main.mp4"
  }
}

```

When [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) processes this EDL, it passes `"eq=contrast=1.2:saturation=1.1"` directly into the filter chain after scaling.

### Via CLI Using grade.py

For dynamic or scriptable workflows, use [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) to generate or validate filters before rendering. The `--filter` flag accepts raw FFmpeg syntax and overrides any preset.

```bash

# Generate a custom filter string

custom_filter="eq=contrast=1.3:saturation=0.95,curves=master='0/0 0.5/0.55 1/1'"

# Apply via grade.py (optional validation step)

python helpers/grade.py sample.mp4 -o /dev/null --filter "$custom_filter"

# Inject into EDL using jq

jq '.grade = env.custom_filter' my_edl.json > tmp.json && mv tmp.json my_edl.json

# Render final output

python helpers/render.py my_edl.json -o final.mp4

```

The `resolve_grade_filter` function handles the logic: if the input matches the pattern `[a-zA-Z0-9_\-]+`, it attempts to load a named preset from [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py). If that fails or the input contains special characters (like `=`, `:`, or `,`), it treats the string as a raw FFmpeg filter and returns it verbatim.

## Filter Chain Order and Processing Context

Understanding the concatenation order is critical for predictable results. The final `-vf` argument follows this sequence:

1. **HDR tone-mapping** (`TONEMAP_CHAIN`) — applied first if the source is HDR
2. **Scaling** — resizes video to target dimensions
3. **Your custom filter** (`grade_filter`) — operates on the final display size
4. **Audio processing** (`afade`) — handled separately via `-af`, never interfering with video filters

Because filters are joined with commas, you can chain multiple FFmpeg filters within the single `grade` string:

```bash
eq=contrast=1.2:saturation=1.1,curves=master='0/0 0.25/0.2 0.75/0.8 1/1',hue=h=10

```

This produces a filter graph like:

```bash
-vf "zscale=...scale=1920:-2,eq=contrast=1.2:saturation=1.1,curves=...,hue=h=10"

```

Your custom processing integrates seamlessly with the pipeline's later stages, including segment concatenation, overlay compositing, subtitle burning, and loudness normalization.

## Practical Code Examples

### Adding a Raw Filter Directly in EDL

Create an EDL file with contrast and saturation adjustments:

```json
{
  "grade": "eq=contrast=1.25:saturation=1.05",
  "ranges": [
    {"source": "intro", "start": 0, "end": 5},
    {"source": "main", "start": 0, "end": 12}
  ],
  "sources": {
    "intro": "raw/intro.mov",
    "main": "raw/main.mp4"
  }
}

```

Execute the render:

```bash
python helpers/render.py my_edl.json -o output.mp4

```

### Overriding Presets with Raw Filters

If you initially selected a preset but need to fine-tune with raw FFmpeg syntax:

```bash

# First, apply a preset

python helpers/grade.py sample.mp4 -o graded.mp4 --preset warm_cinematic

# Later, override with specific filter parameters

python helpers/grade.py graded.mp4 -o final.mp4 \
  --filter "eq=contrast=1.2:saturation=1.1"

```

### Chaining Complex Filter Graphs

For advanced color grading combining multiple FFmpeg filters:

```bash
custom_filter="eq=brightness=0.05:contrast=1.1,unsharp=5:5:1.0:5:5:0.0,vignette=PI/4"

# Add to EDL

jq --arg filter "$custom_filter" '.grade = $filter' my_edl.json > tmp.json
mv tmp.json my_edl.json

# Render

python helpers/render.py my_edl.json -o cinematic_output.mp4

```

## Summary

- **Filter injection point**: [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) → `extract_segment` appends your custom string to the `-vf` chain after HDR conversion and scaling.
- **Input methods**: Add a `"grade"` field to your EDL JSON, or use [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py) with the `--filter` flag.
- **String handling**: Raw FFmpeg syntax passes through `resolve_grade_filter` unchanged; alphanumeric strings trigger preset lookups in [`helpers/grade.py`](https://github.com/browser-use/video-use/blob/main/helpers/grade.py).
- **Compatibility**: Custom filters coexist with built-in tone-mapping, scaling, audio fades, and final compositing stages.
- **Chaining**: Combine multiple filters using commas within the `grade` string for complex processing pipelines.

## Frequently Asked Questions

### What file handles the FFmpeg filter injection in video-use?

The [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) file contains the `extract_segment` function that assembles the FFmpeg command-line. It calls `resolve_grade_filter` to determine the filter string, then appends it to the `vf_parts` list which becomes the final `-vf` argument.

### Can I use multiple FFmpeg filters at once?

Yes. The `grade` field accepts any valid FFmpeg filter string, including comma-separated chains. For example, `"eq=contrast=1.2,curves=master='0/0 1/1',unsharp=3:3:1.0"` applies contrast adjustment, curves, and sharpening sequentially.

### How does custom filtering interact with HDR content?

The pipeline automatically prepends `TONEMAP_CHAIN` before your custom filter if the source is HDR. This ensures tone-mapping from HDR to SDR occurs first, so your custom filter receives standard dynamic range footage at the final display resolution.

### Is there a way to apply different filters to different segments?

Currently, the `grade` field operates at the EDL level (global). To apply different filters per segment, you would need to extend the EDL schema to support a `grade` property within individual range objects, then modify `extract_segment` in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) to check for per-range grade values when processing each segment.