# Understanding the EDL JSON Format: How to Structure an Edit Decision List for video-use

> Master the EDL JSON format for video-use. Learn to structure edit decision lists that power automated video rendering pipelines. Get clear, concise instructions now.

- Repository: [Browser Use/video-use](https://github.com/browser-use/video-use)
- Tags: api-reference
- Published: 2026-07-07

---

**The EDL JSON format in video-use is a structured JSON document that defines video sources, cut ranges, color grading, overlays, and subtitles to drive the entire automated rendering pipeline.**

The `browser-use/video-use` repository treats the **Edit Decision List (EDL)** as the single source of truth that powers every stage of post-production, from segment extraction and color grading to overlay compositing and subtitle burning. Mastering the EDL JSON format is essential for anyone automating video workflows with this tool, as the schema directly dictates how [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) constructs the final output.

## Core Structure of the EDL JSON Format

A valid EDL file is a JSON object with seven top-level fields. The specification is documented in [`SKILL.md`](https://github.com/browser-use/video-use/blob/main/SKILL.md) (lines 268-287) and consumed by the render pipeline in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py).

### Root-Level Fields

The EDL requires these mandatory and optional fields:

- **`version`** – Integer schema version (currently `1`).
- **`sources`** – Object mapping short identifiers (e.g., `"C0103"`) to absolute or relative video file paths.
- **`ranges`** – Ordered array of cut objects defining which segments to extract from which sources.
- **`grade`** – Color grading instruction: either a preset name, a raw ffmpeg filter string, or `"auto"` to trigger per-segment analysis.
- **`overlays`** – Optional array of animation clips to composite on top of the final video.
- **`subtitles`** – Optional path to an SRT file burned in **after** overlays.
- **`total_duration_s`** – Float representing the computed total length of the final video in seconds.

### The ranges Array

Each object in `ranges` defines a single cut with precise timing and metadata:

```json
{
  "source": "C0103",
  "start": 2.42,
  "end": 6.85,
  "beat": "HOOK",
  "quote": "…",
  "reason": "Cleanest delivery, stops before slip at 38.46"
}

```

The `source` value must match a key in the `sources` object. Timestamps are in seconds and support floating-point precision for frame-accurate cuts.

## How the Render Pipeline Consumes the EDL

The [`render.py`](https://github.com/browser-use/video-use/blob/main/render.py) module transforms the ELD JSON into a series of ffmpeg commands through four distinct stages.

### Grade Resolution and Filter Conversion

First, `resolve_grade_filter` (lines 66-85 in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py)) interprets the `grade` field:

- **`"auto"`** – Returns the sentinel `"__AUTO__"` to trigger `auto_grade_for_clip` during segment extraction.
- **Preset name** – Looks up the corresponding ffmpeg filter string via `grade.get_preset`.
- **Raw filter** – Passes the string directly to ffmpeg unchanged.

### Per-Segment Extraction with Audio Fades

For each entry in `ranges`, `extract_all_segments` builds a command that:
- Seeks to the start timestamp using `-ss` for fast, accurate seeking.
- Scales output to **1080p** (or **720p** for draft previews) while preserving portrait orientation via `is_portrait_source` (lines 73-78).
- Applies HDR tone-mapping if the source uses PQ/HLG via `TONEMAP_CHAIN` (lines 95-117).
- Injects the resolved color grade filter.
- Adds a **30ms audio fade-in/out** using `afade` to eliminate clicks at cut points (lines 88-90).

### Lossless Concatenation

After extraction, `concat_segments` (lines 67-82) uses the ffmpeg *concat demuxer* to join all MP4 segments with stream copy (`-c copy`), avoiding generational loss between cuts.

### Overlay and Subtitle Composition

If `overlays` or `subtitles` are present, the pipeline builds a final filter graph that:
- Inserts each overlay clip at its specified `start_in_output` time (PTS shift).
- Applies the `subtitles` filter **last**, using the style constants defined in `SUB_FORCE_STYLE` (lines 51-56) to ensure readability on social platforms.
- Writes the final output with `+faststart` for web streaming optimization.

## Critical Implementation Details

Several hardcoded behaviors in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) affect how you should structure your EDL:

- **HDR Handling** – Sources detected as HDR via `is_hdr_source` automatically receive tone-mapping to prevent oversaturation on SDR displays.
- **Portrait Orientation** – Vertical videos are identified via `is_portrait_source` and scaled accordingly to maintain aspect ratio.
- **Auto-Grade Logic** – When `grade` is `"auto"`, the pipeline executes `auto_grade_for_clip` (lines 34-38) to analyze histogram and contrast per segment.
- **Subtitle Priority** – The SRT specified in `subtitles` is always burned in last, appearing over both video and overlays.
- **Animation Overlays** – Each overlay object requires `file`, `start_in_output`, and `duration` fields to position the animation correctly on the timeline.

## Code Examples

### Building an EDL Programmatically

```python
import json
import pathlib

edl = {
    "version": 1,
    "sources": {
        "C0103": "/abs/path/C0103.MP4",
        "C0108": "/abs/path/C0108.MP4",
    },
    "ranges": [
        {
            "source": "C0103",
            "start": 2.42,
            "end": 6.85,
            "beat": "HOOK",
            "quote": "…",
            "reason": "Cleanest delivery, stops before slip at 38.46."
        },
        {
            "source": "C0108",
            "start": 14.30,
            "end": 28.90,
            "beat": "SOLUTION",
            "quote": "…",
            "reason": "Only take without the false start."
        },
    ],
    "grade": "warm_cinematic",
    "overlays": [
        {"file": "edit/animations/slot_1/render.mp4", "start_in_output": 0.0, "duration": 5.0}
    ],
    "subtitles": "edit/master.srt",
    "total_duration_s": 87.4
}

path = pathlib.Path("edit/edl.json")
path.write_text(json.dumps(edl, indent=2))

```

### Rendering from the Command Line

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

```

### Loading and Inspecting an EDL

```python
with open("edit/edl.json") as f:
    edl = json.load(f)

first_cut = edl["ranges"][0]
print(f"First cut: {first_cut['source']} from {first_cut['start']}s to {first_cut['end']}s")

```

## Summary

- The **EDL JSON format** is the declarative blueprint that `video-use` uses to drive automated video editing.
- **Key fields** include `sources`, `ranges`, `grade`, and optional `overlays`/`subtitles`.
- The **render pipeline** in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py) resolves grades, extracts segments with 30ms audio fades, concatenates losslessly, and composites overlays.
- **HDR sources** are automatically tone-mapped, and **portrait videos** are preserved during scaling.
- **Subtitles** are burned last in the filter chain, ensuring they appear above all other visual elements.

## Frequently Asked Questions

### What values are valid for the `grade` field in an EDL?

The `grade` field accepts three types of values: a preset name (like `"warm_cinematic"`) that maps to a predefined ffmpeg filter, a raw ffmpeg filter string for custom color correction, or the string `"auto"` to trigger per-segment histogram analysis via `auto_grade_for_clip` in the render pipeline.

### How does video-use handle portrait-oriented source videos?

When extracting segments, the pipeline checks `is_portrait_source` (lines 73-78 in [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py)) and adjusts the scale filter to maintain the vertical orientation, ensuring the output matches the original aspect ratio rather than forcing a landscape crop.

### Can I use relative paths in the `sources` object?

Yes. The `sources` mapping accepts both absolute paths and relative paths. The render pipeline resolves these paths relative to the working directory where [`render.py`](https://github.com/browser-use/video-use/blob/main/render.py) is executed, though absolute paths are recommended for CI/CD environments to avoid ambiguity.

### Why is there a 30-millisecond audio fade applied to every segment?

The 30ms fade-in and fade-out (implemented via `afade` on lines 88-90 of [`helpers/render.py`](https://github.com/browser-use/video-use/blob/main/helpers/render.py)) prevents audible clicks and pops that occur when cutting audio at non-zero crossings, ensuring clean transitions between concatenated clips without re-encoding the audio stream.