Optimizing render.py for Large-Scale Video Projects in video-use

To optimize render.py for large-scale video projects, parallelize segment extraction using ThreadPoolExecutor, cache ffprobe calls with lru_cache, utilize RAM disks for temporary files, and optionally bypass the two-pass loudness normalization with a --quick-loudnorm flag.

The render.py script in the browser-use/video-use repository implements a deterministic HEURISTICS pipeline that transforms edit-decision lists (EDL) into finished videos. As projects scale from a handful of clips to hundreds or thousands of segments, the script's sequential, I/O-heavy architecture—spawning at least N+3 FFmpeg processes for N segments—creates severe bottlenecks. Below are proven optimization strategies that maintain the tool's public behavior while dramatically improving throughput.

Understanding the render.py Architecture

Before applying optimizations, it is essential to understand the five-stage pipeline defined in helpers/render.py:

  1. extract_all_segmentsextract_segment: Cuts each range, applies colour-grading (auto or preset), HDR-to-SDR tone-mapping, 30ms audio fades, and rescales to target resolution.
  2. concat_segments: Performs lossless concatenation via the FFmpeg concat demuxer (-c copy).
  3. build_master_srt (optional): Builds subtitle overlays.
  4. build_final_composite: Applies picture-shifted overlays and burns subtitles.
  5. apply_loudnorm_two_pass: Runs two-pass loudness normalization (unless --no-loudnorm is passed).

For a project with 1,000 segments, this workflow spawns over 1,003 subprocesses sequentially. The following strategies address this linear execution penalty.

Parallelize Per-Segment Extraction

The extract_all_segments function processes segments one after another, making it the primary bottleneck. Since each segment operates on an independent source file and writes to its own temporary MP4, this step is embarrassingly parallel.

Replace the sequential loop with a ThreadPoolExecutor to saturate multi-core CPUs:

from concurrent.futures import ThreadPoolExecutor, as_completed
import os

def _extract_one(i, r, sources, edit_dir, preview, draft):
    src_name = r["source"]
    src_path = resolve_path(sources[src_name], edit_dir)
    start = float(r["start"])
    end = float(r["end"])
    duration = end - start
    out_path = edit_dir / f"clips_{'draft' if draft else ('preview' if preview else 'graded')}"
    out_path = out_path / f"seg_{i:02d}_{src_name}.mp4"

    # Auto-grade handling (identical to original implementation)

    seg_filter = (
        auto_grade_for_clip(src_path, start=start, duration=duration, verbose=False)[0]
        if is_auto else resolved
    )
    extract_segment(src_path, start, duration, seg_filter,
                    out_path, preview=preview, draft=draft)
    return out_path

def extract_all_segments(edl, edit_dir, preview, draft=False):
    resolved = resolve_grade_filter(edl.get("grade"))
    is_auto = resolved == "__AUTO__"
    clips_dir = edit_dir / (
        "clips_draft" if draft else ("clips_preview" if preview else "clips_graded")
    )
    clips_dir.mkdir(parents=True, exist_ok=True)

    ranges = edl["ranges"]
    sources = edl["sources"]
    seg_paths = []

    with ThreadPoolExecutor(max_workers=os.cpu_count()) as ex:
        futures = {
            ex.submit(_extract_one, i, r, sources, edit_dir, preview, draft): i
            for i, r in enumerate(ranges)
        }
        for fut in as_completed(futures):
            seg_paths.append(fut.result())

    # Preserve original order (optional)

    seg_paths.sort(key=lambda p: int(p.stem.split('_')[1]))
    return seg_paths

Why this works: FFmpeg handles the heavy lifting (decoding, filtering, re-encoding) in multi-threaded C code. Running multiple FFmpeg processes in parallel via Python threads fully utilizes CPU resources and eliminates the linear wait time of the original loop.

Cache Repeated FFprobe Calls

The pipeline invokes ffprobe twice per unique source: once for HDR detection (is_hdr_source) and once for loudness measurement (measure_loudness). In projects reusing the same source files across multiple segments, these calls duplicate identical work.

Apply functools.lru_cache to reduce subprocess overhead from O(N) to O(unique-sources):

from functools import lru_cache
from pathlib import Path

@lru_cache(maxsize=256)
def is_hdr_source(video: Path) -> bool:
    # Existing ffprobe logic preserved

    ...

@lru_cache(maxsize=256)
def measure_loudness(video_path: Path) -> dict[str, str] | None:
    # Existing loudness measurement logic preserved

    ...

The cache keys results by the absolute file path, ensuring that subsequent segments referencing the same source file reuse the previous probe result without spawning new subprocesses.

Minimize Disk I/O with RAM Disks

Intermediate segment files (*.mp4) dominate the temporary storage footprint and create significant read/write latency. On Linux systems with ample RAM, mount a RAM disk (e.g., /dev/shm) and point the edit_dir there to eliminate physical disk thrashing.

export TMPDIR=/dev/shm/video-use
python helpers/render.py my_project.edl.json -o final.mp4

render.py respects the working directory derived from the EDL location, so no code modification is required. The environment variable simply redirects temporary directories to high-speed memory.

Optimize the Concatenation Strategy

By default, concat_segments uses the FFmpeg concat demuxer with -c copy to create an intermediate base.mp4. If all segments share identical resolution, pixel format, and codec, you can bypass this separate step and concatenate directly within a single FFmpeg filter graph, eliminating one full encode/decode cycle and the temporary base.mp4 file.

ffmpeg -y \
  -i seg_00.mp4 -i seg_01.mp4 -i seg_02.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[outv][outa]" \
  -map "[outv]" -map "[outa]" -c:v libx264 -preset fast -crf 20 final.mp4

Note: When the EDL contains mixed codecs or resolutions, retain the lossless concat demuxer approach to avoid generation loss.

Streamline Loudness Normalization

The apply_loudnorm_two_pass function adds a full render pass for loudness measurement followed by the actual normalization. For large projects where perfect loudness compliance is less critical than turnaround time, expose a --quick-loudnorm flag that uses the one-pass mode already implemented for --preview mode.

ap.add_argument(
    "--quick-loudnorm",
    action="store_true",
    help="Use one-pass loudnorm (faster, slightly less accurate)."
)

# Inside main():

if args.quick_loudnorm:
    apply_loudnorm_two_pass(tmp_composite, out_path, preview=True)
else:
    apply_loudnorm_two_pass(tmp_composite, out_path, preview=args.draft)

This skips the expensive measure_loudness call and the associated ffprobe subprocess, cutting the final processing stage in half.

Tune FFmpeg Threading Parameters

While FFmpeg defaults to automatic thread detection, explicit tuning can improve performance on high-core-count workstations or when processing 4K sources. Pass -threads 0 (auto-detect) or a specific value through extract_segment and the final composite call:

cmd = [
    "ffmpeg", "-y", "-threads", "0",
    "-ss", f"{seg_start:.3f}",
    # ... remaining arguments

]

This ensures FFmpeg fully utilizes available CPU resources during the encode/decode phases within each segment processing task.

Summary

  • Parallelize extraction using ThreadPoolExecutor in extract_all_segments to eliminate the sequential processing bottleneck.
  • Cache probe results with lru_cache on is_hdr_source and measure_loudness to reduce redundant ffprobe subprocesses.
  • Use RAM disks by setting TMPDIR to /dev/shm to minimize disk I/O latency for temporary MP4 files.
  • Batch concatenation via FFmpeg filter graphs (when codec uniformity allows) to skip the intermediate lossless concat file.
  • Skip two-pass loudnorm by implementing --quick-loudnorm for faster, single-pass audio normalization.
  • Tune threading with explicit -threads parameters to maximize CPU utilization per FFmpeg process.

Frequently Asked Questions

Does parallel extraction affect video quality?

No. Parallel extraction preserves the exact same FFmpeg command arguments and filter graphs used in the sequential version. Each segment is rendered independently to its own temporary file, so there is no risk of cross-contamination or quality degradation. The final concatenation step remains identical, ensuring bit-exact output compared to the original sequential implementation.

How much RAM do I need for the RAM disk optimization?

Allocate enough space to hold all intermediate segment files simultaneously. For a 1080p project with 1,000 segments averaging 50MB each, approximately 50GB of free RAM is required. If physical memory is constrained, use the parallel extraction optimization instead, as the RAM disk strategy is only beneficial when the working set fits entirely in memory without swapping.

Can I use these optimizations with preview mode?

Yes. The preview flag in extract_all_segments triggers lower-resolution processing that benefits even more from parallelization due to reduced per-segment processing time. The --quick-loudnorm flag is particularly effective in preview mode, as it uses the existing one-pass loudness logic intended for draft renders, providing nearly instant feedback on audio levels.

Why does the batch concatenation strategy require identical codecs?

The batch concatenation strategy uses FFmpeg's concat filter, which decodes all inputs, concatenates them in the filter graph, and then re-encodes the output. If inputs have different codecs, resolutions, or pixel formats, the filter graph fails or requires complex normalization filters. The default concat demuxer (-c copy) avoids re-encoding by stream copying, which is safer for heterogeneous EDLs but requires the intermediate file. Use batch concatenation only when your entire EDL uses uniform export settings from helpers/grade.py.

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 →