How video-use Performs HDR to SDR Tone Mapping with zscale and Hable
The video-use repository automatically converts HDR10 and HLG sources to Rec. 709 SDR using a FFmpeg filter chain that combines zscale for color space conversion and the Hable tonemap operator for perceptual roll-off.
The browser-use/video-use project handles modern video workflows where source footage may arrive in HDR formats (PQ/Rec. 2020 or HLG) but final delivery requires standard SDR. Understanding how this HDR to SDR tone mapping is implemented helps you customize the rendering pipeline or debug color issues in your exported videos.
HDR Detection in video-use
Before applying any conversion filters, the system must identify whether the source material contains HDR metadata.
How HDR Sources Are Identified
The is_hdr_source() function in helpers/render.py probes the first video stream using ffprobe to check the color_transfer characteristic. This prevents unnecessary processing on already-SDR footage.
HDR_TRANSFERS = {"smpte2084", "arib-std-b67"} # PQ (HDR10) and HLG
def is_hdr_source(video: Path) -> bool:
out = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=color_transfer",
"-of", "default=noprint_wrappers=1:nokey=1", str(video)],
capture_output=True, text=True, check=True,
)
return out.stdout.strip() in HDR_TRANSFERS
This detection runs automatically during segment extraction (lines 108–130 in helpers/render.py), ensuring that only HDR inputs trigger the tone-mapping pipeline.
The zscale and Hable Tonemap Filter Chain
Once HDR is detected, video-use prepends a specific TONEMAP_CHAIN to the FFmpeg video filter graph. This chain handles the complete transformation from HDR to SDR in five stages.
Breaking Down the TONEMAP_CHAIN
The constant defined in helpers/render.py (lines 111–115) constructs a linear workflow that preserves color fidelity during conversion:
TONEMAP_CHAIN = (
"zscale=t=linear:npl=100," # linearise 10‑bit Rec.2020
"format=gbrpf32le," # work in high‑precision float
"zscale=p=bt709," # chroma‑convert to BT.709 primaries
"tonemap=tonemap=hable:desat=0," # Hable curve → SDR
"zscale=t=bt709:m=bt709:r=tv," # final 8‑bit BT.709 transfer
"format=yuv420p" # FFmpeg‑compatible pixel format
)
The process works as follows:
- Linearisation (
zscale=t=linear:npl=100): Converts the HDR gamma curve (PQ or HLG) to linear light with a nominal peak luminance of 100 nits, preparing the data for mathematical tone mapping. - High-precision format (
format=gbrpf32le): Switches to 32-bit floating point to prevent banding during the aggressive tonal compression. - Gamut conversion (
zscale=p=bt709): Transforms the wide Rec. 2020 color primaries to the narrower Rec. 709 standard. - Hable tonemap (
tonemap=tonemap=hable:desat=0): Applies the Hable filmic curve to compress highlights smoothly into SDR range without desaturating colors. - Output normalization (
zscale=t=bt709:m=bt709:r=tv,format=yuv420p): Applies the BT.709 transfer curve and converts to 8-bityuv420pfor final encoding.
Integration into the Rendering Pipeline
The tone-mapping chain is injected during segment extraction in helpers/render.py (lines 79–86). The code builds a filter list dynamically, inserting HDR conversion before any scaling or color grading operations.
vf_parts: list[str] = []
if is_hdr_source(source):
vf_parts.append(TONEMAP_CHAIN) # ← HDR → SDR conversion
vf_parts.append(scale) # 1080p/720p scaling
if grade_filter:
vf_parts.append(grade_filter) # optional colour‑grade
vf = ",".join(vf_parts)
This ordering ensures that scaling and grading operate on the SDR signal, preventing the washed-out or clipped appearance that occurs when filters process HDR luminance values directly.
Code Examples
CLI Usage
Render a project with automatic HDR handling:
# Assuming you have an EDL JSON at ./edl.json
python helpers/render.py edl.json -o final.mp4
The script will:
- Probe each source video for
color_transfermetadata - Detect HDR via
is_hdr_source() - Prepend the
TONEMAP_CHAINto the FFmpeg-vfargument - Produce an SDR
final.mp4compatible with all platforms
Programmatic Usage
Import the helpers directly for custom workflows:
from pathlib import Path
from helpers.render import extract_segment, is_hdr_source, TONEMAP_CHAIN
src = Path("raw/iphone_clip.mov")
if is_hdr_source(src):
print("HDR detected – applying tone‑map")
else:
print("SDR source – no tone‑map needed")
# Extract a 5‑second clip with automatic HDR handling
extract_segment(
source=src,
seg_start=10.0,
duration=5.0,
grade_filter="", # no extra grade
out_path=Path("tmp/clip.mp4"),
preview=False,
draft=False,
)
Summary
- HDR Detection: The
is_hdr_source()function inhelpers/render.pyidentifies PQ (smpte2084) and HLG (arib-std-b67) content viaffprobe. - zscale Pipeline: The linearisation and gamut conversion use
zscalefilters to move from Rec. 2020 to Rec. 709 before tone mapping. - Hable Tonemap: The
tonemap=hableoperator provides perceptual highlight compression that mimics professional display processing. - Automatic Injection: The
TONEMAP_CHAINis prepended to the filter graph only when HDR is detected, ensuring SDR sources remain untouched.
Frequently Asked Questions
What is the Hable tonemap operator and why does video-use use it?
The Hable tonemap is a filmic curve designed to compress high-dynamic-range luminance into standard dynamic range while preserving perceptual contrast. According to the video-use source code, the tonemap=hable:desat=0 setting applies this curve without desaturating colors, avoiding the flat, oversaturated look that occurs when HDR metadata is simply stripped or ignored.
How does video-use detect HDR content automatically?
The repository uses the is_hdr_source() function to run ffprobe against the first video stream and read the color_transfer tag. If the value matches smpte2084 (HDR10/PQ) or arib-std-b67 (HLG), the function returns True and triggers the HDR to SDR conversion pipeline.
Can I modify the TONEMAP_CHAIN parameters for custom conversion?
Yes. The TONEMAP_CHAIN is defined as a module-level constant in helpers/render.py (lines 111–115). You can edit the string to adjust parameters like npl (nominal peak luminance) or swap hable for other operators like mobius or reinhard, though this requires direct modification of the source file as the current implementation does not expose these parameters via the CLI.
Does the tone mapping process affect extraction performance?
The HDR to SDR conversion adds computational overhead due to the 32-bit floating-point processing (gbrpf32le) and multiple zscale operations. However, the filter chain is only applied when is_hdr_source() returns True, so SDR sources render at full speed without the penalty of these additional filter stages.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →