How video-use Applies Loudness Normalization to Hit -14 LUFS for Social Media
video-use automatically normalizes audio to -14 LUFS during the final render using a two-pass FFmpeg loudnorm filter, ensuring compliance with YouTube, Instagram, TikTok, and LinkedIn standards.
The browser-use/video-use repository implements automated loudness normalization as a core step in its video rendering pipeline. By hard-coding industry-standard targets and executing a precise two-pass FFmpeg workflow, the tool ensures that exported videos meet the strict audio level requirements demanded by modern social platforms. This eliminates manual audio leveling and prevents videos from being penalized by platform algorithms for inconsistent loudness.
Social Media Target Constants
The normalization targets are defined as module-level constants in helpers/render.py to match the "social-ready" profile used by major platforms.
# helpers/render.py
# Social-media standard: -14 LUFS integrated, -1 dBTP peak, LRA 11 LU.
# Matches YouTube / Instagram / TikTok / X / LinkedIn normalization targets.
LOUDNORM_I = -14.0 # ← target integrated loudness
LOUDNORM_TP = -1.0 # ← target true-peak
LOUDNORM_LRA = 11.0 # ← target loudness range
These values correspond to -14 LUFS integrated loudness, -1 dBTP true-peak limiting, and a 11 LU loudness range. According to the source code comments at lines 690-695, this specific profile ensures that rendered videos upload to social platforms without triggering additional platform-side compression or normalization.
Two-Pass Measurement with FFmpeg
Before applying correction, the system must measure the source audio characteristics. The measure_loudness() function runs a first-pass analysis using FFmpeg's loudnorm filter in JSON print mode.
def measure_loudness(video_path: Path) -> dict[str, str] | None:
filter_str = f"loudnorm=I={LOUDNORM_I}:TP={LOUDNORM_TP}:LRA={LOUDNORM_LRA}:print_format=json"
cmd = ["ffmpeg", "-y", "-hide_banner", "-nostats",
"-i", str(video_path), "-af", filter_str,
"-vn", "-f", "null", "-"]
proc = subprocess.run(cmd, capture_output=True, text=True)
# …extract JSON from proc.stderr…
This analysis pass (lines 998-1005) extracts the input file's measured integrated loudness (input_i), true-peak (input_tp), loudness range (input_lra), threshold (input_thresh), and the calculated target offset. The function returns these values as a dictionary to be consumed by the second pass.
Applying the Normalization Filter
The apply_loudnorm_two_pass() function implements the actual correction logic. It distinguishes between preview mode (fast one-pass) and final render mode (accurate two-pass).
For final renders, the function feeds the measured values back into FFmpeg alongside the target constants:
def apply_loudnorm_two_pass(input_path: Path, output_path: Path, preview: bool = False) -> bool:
if preview:
# one-pass approximation
filter_str = f"loudnorm=I={LOUDNORM_I}:TP={LOUDNORM_TP}:LRA={LOUDNORM_LRA}"
# …
else:
measurement = measure_loudness(input_path)
filter_str = (
f"loudnorm=I={LOUDNORM_I}:TP={LOUDNORM_TP}:LRA={LOUDNORM_LRA}"
f":measured_I={measurement['input_i']}"
f":measured_TP={measurement['input_tp']}"
f":measured_LRA={measurement['input_lra']}"
f":measured_thresh={measurement['input_thresh']}"
f":offset={measurement['target_offset']}:linear=true"
)
# …
As implemented in lines 1020-1070, the two-pass approach uses the linear=true parameter for true-peak limiting and applies the measured offset to achieve the -14 LUFS target precisely. Preview mode skips the measurement phase for speed, using a single-pass approximation instead.
Integration in the Rendering Pipeline
Loudness normalization is integrated into the final compositing workflow in helpers/render.py (lines 644-652). After visual overlays and subtitles are composited, the script either writes directly to the destination or processes through the loudnorm pipeline:
# helpers/render.py – main rendering flow (excerpt)
if args.no_loudnorm:
build_final_composite(base_path, overlays, subs_path, out_path, edit_dir)
else:
tmp_composite = out_path.with_suffix(".prenorm.mp4")
build_final_composite(base_path, overlays, subs_path, tmp_composite, edit_dir)
print("loudness normalization → social-ready (-14 LUFS / -1 dBTP / LRA 11)")
apply_loudnorm_two_pass(tmp_composite, out_path, preview=args.draft)
tmp_composite.unlink(missing_ok=True)
The default behavior includes normalization. The system creates a temporary "pre-norm" file, applies the two-pass loudness correction, then deletes the temporary file. Users can skip this step entirely using the --no-loudnorm flag for workflows requiring custom audio processing.
Command-Line Usage Examples
Default render (social-ready loudness):
python helpers/render.py my_edit.edl.json -o final.mp4
Result: Composite → temporary pre-norm → two-pass loudnorm → final.mp4 at -14 LUFS.
Skip loudness normalization:
python helpers/render.py my_edit.edl.json -o final.mp4 --no-loudnorm
Result: Composite writes directly to final.mp4 without audio normalization, preserving original levels for external processing.
Preview render (fast one-pass):
python helpers/render.py my_edit.edl.json -o preview.mp4 --preview --draft
Result: Uses the one-pass approximation in apply_loudnorm_two_pass(..., preview=True) for faster iteration during editing.
Summary
- -14 LUFS target is hard-coded via
LOUDNORM_I,LOUDNORM_TP, andLOUDNORM_LRAconstants inhelpers/render.pyto match YouTube, Instagram, TikTok, and LinkedIn standards. - Two-pass measurement occurs via
measure_loudness()using FFmpeg's JSON output mode to capture precise input characteristics. - Correction application happens in
apply_loudnorm_two_pass(), which uses measured values for final renders and a fast approximation for preview mode. - Automatic integration runs after visual compositing unless disabled with
--no-loudnorm, ensuring all exports are "social-ready" without manual intervention.
Frequently Asked Questions
Why does video-use target -14 LUFS instead of -4 LUFS?
The -14 LUFS target aligns with the normalization standards used by YouTube, Instagram Reels, TikTok, X, and LinkedIn. As noted in the source code comments at helpers/render.py lines 690-695, these constants ensure videos are "social-ready" immediately after export. Users requiring broadcast standards (-4 LUFS) must manually modify the LOUDNORM_I, LOUDNORM_TP, and LOUDNORM_LRA constants in the source file.
Can I skip loudness normalization for custom audio workflows?
Yes. Pass the --no-loudnorm flag to render.py to bypass the normalization pipeline entirely. When this flag is used, build_final_composite() writes the video directly to the output path without creating temporary pre-norm files or running the loudnorm filter, preserving the original audio levels for external processing.
What is the difference between preview mode and final render for loudness?
Preview mode uses a one-pass approximation via apply_loudnorm_two_pass(..., preview=True) for speed during iterative editing. The final render uses a two-pass process: first calling measure_loudness() to analyze the source, then applying precise corrections using the measured input values (measured_I, measured_TP, etc.) to achieve the exact -14 LUFS target.
Which source file contains the loudness normalization implementation?
All loudness logic resides in helpers/render.py. The target constants are defined at lines 690-695, the measurement function measure_loudness() is implemented at lines 998-1005, and the application function apply_loudnorm_two_pass() spans lines 1020-1070. The integration into the main rendering flow occurs at lines 644-652.
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 →