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

You can integrate custom FFmpeg filters into video-use by supplying a raw filter string through the EDL grade field or the grade.py CLI, which 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, 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:


# 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, 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.

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.

{
  "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 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 to generate or validate filters before rendering. The --filter flag accepts raw FFmpeg syntax and overrides any preset.


# 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. 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:

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:

-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:

{
  "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:

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:


# 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:

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 → 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 with the --filter flag.
  • String handling: Raw FFmpeg syntax passes through resolve_grade_filter unchanged; alphanumeric strings trigger preset lookups in 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 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 to check for per-range grade values when processing each segment.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →