### Install ffmpeg-normalize via uv Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/installation.md Use this command to install ffmpeg-normalize using the uvx utility. This is the recommended installation method. ```bash uvx ffmpeg-normalize ``` -------------------------------- ### Install and Run ffmpeg-normalize Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/index.md Install the tool using pip or uv, then run it on a media file. The normalized file will be saved in a 'normalized' subdirectory. ```bash pip3 install ffmpeg-normalize ``` ```bash ffmpeg-normalize /path/to/your/file.mp4 ``` ```bash uvx ffmpeg-normalize /path/to/your/file.mp4 ``` -------------------------------- ### Install Bash Shell Completions Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/installation.md Installs the ffmpeg-normalize bash completion script. This method assumes bash-completion is installed. ```bash curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize.bash \ -o /usr/local/etc/bash_completion.d/ffmpeg-normalize ``` -------------------------------- ### Install Dependencies with uv Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Install project dependencies using uv, the recommended package manager. Ensure you are in the project's root directory. ```bash uv sync --group dev ``` -------------------------------- ### Install Zsh Shell Completions Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/installation.md Installs the ffmpeg-normalize zsh completion script by downloading it to the default site-functions directory. ```bash curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize.zsh \ -o /usr/local/share/zsh/site-functions/ ``` -------------------------------- ### Install ffmpeg on Linux (Static Build) Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/requirements.md Use this method to install ffmpeg on Linux if distribution packages are outdated. It involves downloading a static build and copying the executables to your PATH. ```bash wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz mkdir -p ffmpeg tar -xf ffmpeg-release-amd64-static.tar.xz -C ffmpeg --strip-components=1 sudo cp ffmpeg/ffmpeg /usr/local/bin sudo cp ffmpeg/ffprobe /usr/local/bin sudo chmod +x /usr/local/bin/ffmpeg /usr/local/bin/ffprobe ``` -------------------------------- ### Verify Setup with Tests and Help Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Run the test suite to ensure your environment is set up correctly. You can also test the command-line interface by running the help command. ```bash # Run tests uv run pytest # Run the tool uv run python -m ffmpeg_normalize --help ``` -------------------------------- ### Dynamic Normalization Examples Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/audio-filters.md Apply dynamic audio compression to smooth out volume differences in the audio signal. Use these examples for low, mid, and high compression levels. ```bash ffmpeg-normalize test.wav -prf "dynaudnorm=p=0.9:s=0" ``` ```bash ffmpeg-normalize test.wav -prf "dynaudnorm=p=0.5:s=5" ``` ```bash ffmpeg-normalize test.wav -prf "dynaudnorm=p=0.3:s=15" ``` -------------------------------- ### Install ffmpeg on macOS and Linux (Homebrew) Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/requirements.md Install ffmpeg using Homebrew on macOS and Linux. This is a convenient method but may install additional dependencies. ```bash brew install ffmpeg ``` -------------------------------- ### Install ffmpeg-normalize via pipx Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/installation.md Install ffmpeg-normalize using pipx, a tool for installing and running Python applications in isolated environments. ```bash pipx install ffmpeg-normalize ``` -------------------------------- ### Normalizing with AAC Codec Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/basic.md This example demonstrates normalizing an input file and encoding the audio using the AAC codec. ```bash ffmpeg-normalize input1.mp3 -c:a aac ``` -------------------------------- ### Customizing Audio Codec and Bitrate Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/basic.md This example shows how to specify a different audio codec (AAC) and bitrate (192 kbps) for the output file to manage file size. ```bash ffmpeg-normalize input.mp3 -c:a aac -b:a 192k ``` -------------------------------- ### Override Preset Options Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/presets.md When using a preset, command-line options take precedence. This example uses the 'podcast' preset but overrides the audio codec to 'libmp3lame'. ```bash ffmpeg-normalize input.mp3 --preset podcast --audio-codec libmp3lame ``` -------------------------------- ### Denoising Examples Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/audio-filters.md Apply a denoiser filter like `anlmdn` to remove background noise. Examples are provided for low, mid, and high denoising levels. ```bash ffmpeg-normalize test.wav -prf "anlmdn=s=0.0001:p=0.1:m=15" ``` ```bash ffmpeg-normalize test.wav -prf "anlmdn=s=0.0001:p=0.01:m=15" ``` ```bash ffmpeg-normalize test.wav -prf "anlmdn=s=0.001:p=0.01:m=15" ``` -------------------------------- ### Manually Install Bash Shell Completions Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/installation.md Manually installs the ffmpeg-normalize bash completion script without requiring bash-completion. It creates a directory, downloads the script, and sources it in .bashrc. ```bash # create completions directory if it doesn't exist mkdir -p ~/.bash_completions.d # download and install completion script curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize.bash \ -o ~/.bash_completions.d/ffmpeg-normalize # source it in your ~/.bashrc echo 'source ~/.bash_completions.d/ffmpeg-normalize' >> ~/.bashrc ``` -------------------------------- ### Install ffmpeg-normalize via pip Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/installation.md Install ffmpeg-normalize using pip for Python 3. The --user flag installs it for the current user only. ```bash pip3 install --user ffmpeg-normalize ``` -------------------------------- ### Conventional Commit Examples Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Examples of commit messages following the Conventional Commits specification. Use appropriate types like 'feat', 'fix', 'docs', etc. ```bash git commit -m "feat: add selective audio stream normalization" git commit -m "fix: apply extra input options to first pass" git commit -m "docs: update contributing guide" ``` -------------------------------- ### Specifying Custom Output Extension Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/basic.md This example uses the -ext option to set a common output file extension (m4a) for all normalized files, while also specifying the AAC codec. ```bash ffmpeg-normalize input.mp3 -c:a aac -ext m4a ``` -------------------------------- ### Pre-processing with a Limiter Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/album-batch-normalization.md Apply limiting before normalization using the `--pre-filter` option to manage clipping. This example limits to 0.99 and encodes to FLAC. ```bash ffmpeg-normalize album/*.flac --batch -nt rms -t -20 -prf "alimiter=limit=0.99" -c:a flac ``` -------------------------------- ### Normalizing Specific Stream and Keeping Others Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/basic.md This example normalizes a specific audio stream (stream 1) and uses --keep-other-audio to ensure all other audio streams are copied to the output without modification. ```bash # Normalize stream 1, keep all other audio streams as-is ffmpeg-normalize input.mkv -as 1 --keep-other-audio ``` -------------------------------- ### Preview Local Documentation Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Serve the documentation locally using MkDocs to preview changes. This command requires the 'mkdocs-material' theme. ```bash uvx --with mkdocs-material mkdocs serve ``` -------------------------------- ### Install Dependencies with pip Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Alternatively, install project dependencies using pip. This command installs the package in editable mode with development dependencies. ```bash pip install -e ".[dev]" ``` -------------------------------- ### Create MKdocs Releases with uvx Source: https://github.com/slhck/ffmpeg-normalize/blob/master/DEVELOPERS.md Deploy documentation using MKdocs and Material theme via uvx. This command is typically handled by CI. ```bash uvx --with mkdocs-material mkdocs gh-deploy ``` -------------------------------- ### Use Built-in and Custom Presets Source: https://context7.com/slhck/ffmpeg-normalize/llms.txt Apply presets stored in JSON files for consistent option sets. Use --list-presets to see available presets and --preset to apply one. ```bash # List all available presets ffmpeg-normalize --list-presets ``` ```bash # Apply the podcast preset (EBU R128, -16 LUFS, LRT 7.0, TP -2.0) ffmpeg-normalize interview.wav --preset podcast -o interview_norm.wav ``` ```bash # Apply the music preset (RMS batch mode, -20 dB) — CLI flag overrides preset ffmpeg-normalize album/*.flac --preset music -t -18 -ext flac ``` ```bash # Apply the streaming-video preset (EBU R128, -14 LUFS) ffmpeg-normalize episode.mp4 --preset streaming-video -c:a aac -ext mp4 -o episode_norm.mp4 ``` ```json // podcast { "normalization-type": "ebu", "target-level": -16.0, "loudness-range-target": 7.0, "true-peak": -2.0, "progress": true } ``` ```json // music { "normalization-type": "rms", "target-level": -20.0, "batch": true, "progress": true } ``` ```json // streaming-video { "normalization-type": "ebu", "target-level": -14.0, "loudness-range-target": 7.0, "true-peak": -2.0, "progress": true } ``` ```json // Custom preset example — save as ~/.config/ffmpeg-normalize/presets/voice.json: { "normalization-type": "ebu", "target-level": -18.0, "loudness-range-target": 4.0, "true-peak": -3.0, "audio-codec": "libopus", "audio-bitrate": "96k", "audio-channels": 1, "progress": true } ``` ```bash ffmpeg-normalize voice_recording.wav --preset voice ``` -------------------------------- ### High-Pass Filtering Example Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/audio-filters.md Use a high-pass filter to remove low rumbling noise from the audio signal. This example sets the cutoff frequency to 100 Hz. ```bash ffmpeg-normalize test.wav -prf "highpass=f=100" ``` -------------------------------- ### Run Normalization Process Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/api/ffmpeg_normalize.html Executes the full audio normalization process, including the first pass for loudness statistics and the second pass for applying normalization. Includes an option to run replay gain as a shortcut. ```python def run_normalization(self) -> None: """ Run the normalization process for this file. """ _logger.debug(f"Running normalization for {self.input_file}") # run the first pass to get loudness stats self._first_pass() # shortcut to apply replaygain if self.ffmpeg_normalize.replaygain: self._run_replaygain() return # run the second pass as a whole if self.ffmpeg_normalize.progress: with tqdm( total=100, position=1, desc="Second Pass", bar_format=TQDM_BAR_FORMAT, ) as pbar: for progress in self._second_pass(): pbar.update(progress - pbar.n) else: for _ in self._second_pass(): pass _logger.info(f"Normalized file written to {self.output_file}") ``` -------------------------------- ### FFmpegNormalize Class Initialization and Basic Usage Source: https://context7.com/slhck/ffmpeg-normalize/llms.txt Demonstrates how to initialize the FFmpegNormalize class with EBU R128 normalization parameters and add media files for processing. ```APIDOC ## FFmpegNormalize Class Initialization and Basic Usage ### Description Initialize the `FFmpegNormalize` class with EBU R128 normalization parameters, then add input and output file pairs using `add_media_file()`. Finally, execute the normalization process with `run_normalization()`. ### Method Signature ```python FFmpegNormalize(normalization_type: str, target_level: float, loudness_range_target: float, true_peak: float, audio_codec: str, audio_bitrate: str, sample_rate: int, print_stats: bool, progress: bool) ``` ### Parameters * **normalization_type** (str) - Type of normalization: "ebu", "rms", or "peak". * **target_level** (float) - Target loudness level in LUFS (EBU) or dB (rms/peak). * **loudness_range_target** (float) - Target loudness range (LRA) in LU (1–50). * **true_peak** (float) - Maximum true peak level in dBTP (-9 to 0). * **audio_codec** (str) - Audio codec for the output file (e.g., "aac"). * **audio_bitrate** (str) - Audio bitrate for the output file (e.g., "192k"). * **sample_rate** (int) - Sample rate for the output audio (e.g., 44100). * **print_stats** (bool) - If True, emit JSON stats to stdout. * **progress** (bool) - If True, show progress during normalization. ### Method Calls * `add_media_file(input_path: str, output_path: str)`: Registers an input/output file pair. * `run_normalization()`: Executes the normalization process for all registered files. ### Error Handling * `FFmpegNormalizeError`: Raised if normalization fails. ### Request Example ```python from ffmpeg_normalize import FFmpegNormalize, FFmpegNormalizeError normalizer = FFmpegNormalize( normalization_type="ebu", # "ebu" | "rms" | "peak" target_level=-23.0, # LUFS (EBU) or dB (rms/peak) loudness_range_target=7.0, # LRA target, 1–50 LU true_peak=-2.0, # max true peak, -9 to 0 dBTP audio_codec="aac", audio_bitrate="192k", sample_rate=44100, print_stats=True, progress=True, ) try: normalizer.add_media_file("input.mp4", "output.mp4") normalizer.add_media_file("input2.mp4", "output2.mp4") normalizer.run_normalization() except FFmpegNormalizeError as exc: print(f"Normalization failed: {exc}") ``` ``` -------------------------------- ### Get Loudness Statistics Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Retrieves loudness statistics for all audio streams within the media file. ```python return ( audio_stream.get_stats() for audio_stream in self.streams["audio"].values() ) ``` -------------------------------- ### Upgrade ffmpeg-normalize via pip Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/installation.md Upgrade an existing ffmpeg-normalize installation to the latest version using pip. ```bash pip3 install --upgrade --user ffmpeg-normalize ``` -------------------------------- ### Generate API Documentation Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Generate API documentation using pdoc. This command creates documentation in Google style and outputs it to the 'docs-api' directory. ```bash pdoc -d google -o docs-api ./ffmpeg_normalize ``` -------------------------------- ### Get All Stream IDs Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Retrieves a list of all stream IDs (audio, video, and subtitle) present in the media file. ```python def _stream_ids(self) -> list[int]: """ Get all stream IDs of this file. Returns: list: List of stream IDs """ return ( list(self.streams["audio"].keys()) + list(self.streams["video"].keys()) + list(self.streams["subtitle"].keys()) ) ``` -------------------------------- ### Print Version Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/cli-options.md Use the --version flag to display the current version of ffmpeg-normalize and exit. ```bash ffmpeg-normalize --version ``` -------------------------------- ### Run Normalization Process Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Executes the audio normalization process, including a first pass for loudness statistics and a second pass, potentially using a temporary file for ReplayGain. ```python def run_normalization(self) -> None: """ Run the normalization process for this file. """ _logger.debug(f"Running normalization for {self.input_file}") # run the first pass to get loudness stats self._first_pass() # for second pass, create a temp file temp_dir = mkdtemp() self.temp_file = os.path.join(temp_dir, f"out.{self.output_ext}") if self.ffmpeg_normalize.replaygain: _logger.debug( "ReplayGain mode: Second pass will run with temporary file to get stats." ) self.output_file = self.temp_file # run the second pass as a whole. if self.ffmpeg_normalize.progress: with tqdm( total=100, ``` -------------------------------- ### Get PCM Codec Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Determines and returns the appropriate PCM codec string based on the stream's bit depth. ```APIDOC ## get_pcm_codec() -> str ### Description Get the PCM codec string for the stream. ### Returns - str: The PCM codec string. ### Example ```python # Assuming 'stream' is an instance of AudioStream pcm_codec = stream.get_pcm_codec() print(f"PCM Codec: {pcm_codec}") ``` ``` -------------------------------- ### Initialize MediaFile Object Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Initializes a MediaFile object, setting up references to FFmpegNormalize settings, input, and output file paths. It also determines the output file extension. ```python def __init__( self, ffmpeg_normalize: FFmpegNormalize, input_file: str, output_file: str ): """ Initialize a media file for later normalization by parsing the streams. Args: ffmpeg_normalize (FFmpegNormalize): reference to overall settings input_file (str): Path to input file output_file (str): Path to output file """ self.ffmpeg_normalize = ffmpeg_normalize self.skip = False self.input_file = input_file self.output_file = output_file current_ext = os.path.splitext(output_file)[1][1:] # we need to check if it's empty, e.g. /dev/null or NUL if current_ext == "" or self.output_file == os.devnull: _logger.debug( f"Current extension is unset, or output file is a null device, using extension: {self.ffmpeg_normalize.extension}" ) ``` -------------------------------- ### Set Output Folder and Extension Defaults Source: https://context7.com/slhck/ffmpeg-normalize/llms.txt Specify a different output folder using -of or change the default output extension for files without an explicit -o path using -ext. ```bash # Output to a specific folder instead of the default `normalized/` ffmpeg-normalize input1.mp3 input2.mp3 -of /tmp/normalized ``` ```bash # Change the default output extension (affects files with no explicit -o path) ffmpeg-normalize input.mp3 -ext aac -c:a aac ``` -------------------------------- ### Get Loudness Statistics Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Retrieves the loudness statistics for an audio stream, including EBU pass 1 and 2, and mean/max loudness values. ```APIDOC ## get_stats() ### Description Return loudness statistics for the stream. ### Returns - dict: A dictionary containing the loudness statistics. ### Response Example ```json { "input_file": "string", "output_file": "string", "stream_id": "integer", "ebu_pass1": "object", "ebu_pass2": "object", "mean": "float", "max": "float" } ``` ``` -------------------------------- ### Get Filter String with Pre-filter - Python Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Constructs a filter string for FFmpeg, prepending a pre-filter if defined. Applies the input stream label. ```python input_label = f"[0:{self.stream_id}]" filter_chain = [] if self.media_file.ffmpeg_normalize.pre_filter: filter_chain.append(self.media_file.ffmpeg_normalize.pre_filter) filter_chain.append(current_filter) filter_str = input_label + ",".join(filter_chain) return filter_str ``` -------------------------------- ### Get Loudness Statistics Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Retrieves loudness statistics for all audio streams within a media file. This is useful for analyzing audio levels before or after normalization. ```python def get_stats(self) -> Iterable[ffmpeg_normalize._streams.LoudnessStatisticsWithMetadata]: return ( audio_stream.get_stats() for audio_stream in self.streams["audio"].values() ) ``` -------------------------------- ### Initialize MediaFile Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/api/ffmpeg_normalize.html Initializes a MediaFile object, parsing its streams and setting up output file details. It determines the output file extension based on the provided output path or the FFmpegNormalize settings. ```python class MediaFile: def __init__( self, ffmpeg_normalize: FFmpegNormalize, input_file: str, output_file: str ): """ Initialize a media file for later normalization by parsing the streams. Args: ffmpeg_normalize (FFmpegNormalize): reference to overall settings input_file (str): Path to input file output_file (str): Path to output file """ self.ffmpeg_normalize = ffmpeg_normalize self.skip = False self.input_file = input_file self.output_file = output_file current_ext = os.path.splitext(output_file)[1][1:] # we need to check if it's empty, e.g. /dev/null or NUL if current_ext == "" or self.output_file == os.devnull: self.output_ext = self.ffmpeg_normalize.extension else: self.output_ext = current_ext self.streams: StreamDict = {"audio": {}, "video": {}, "subtitle": {}} self.parse_streams() ``` -------------------------------- ### Run Audio Normalization Process Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs-api/ffmpeg_normalize.html Executes the full audio normalization pipeline, including first and second passes, and optional ReplayGain tagging. Handles temporary file creation and cleanup. ```python def run_normalization(self) -> None: """ Run the normalization process for this file. """ _logger.debug(f"Running normalization for {self.input_file}") # run the first pass to get loudness stats, unless in dynamic EBU mode if not ( self.ffmpeg_normalize.dynamic and self.ffmpeg_normalize.normalization_type == "ebu" ): self._first_pass() else: _logger.debug( "Dynamic EBU mode: First pass will not run, as it is not needed." ) # for second pass, create a temp file temp_dir = mkdtemp() self.temp_file = os.path.join(temp_dir, f"out.{self.output_ext}") if self.ffmpeg_normalize.replaygain: _logger.debug( "ReplayGain mode: Second pass will run with temporary file to get stats." ) self.output_file = self.temp_file # run the second pass as a whole. if self.ffmpeg_normalize.progress: with tqdm( total=100, position=1, desc="Second Pass", bar_format=TQDM_BAR_FORMAT, ) as pbar: for progress in self._second_pass(): pbar.update(progress - pbar.n) else: for _ in self._second_pass(): pass # remove temp dir; this will remove the temp file as well if it has not been renamed (e.g. for replaygain) if os.path.exists(temp_dir): rmtree(temp_dir, ignore_errors=True) # This will use stats from ebu_pass2 if available (from the main second pass), # or fall back to ebu_pass1. if self.ffmpeg_normalize.replaygain: _logger.debug( "ReplayGain tagging is enabled. Proceeding with tag calculation/application." ) self._run_replaygain() if not self.ffmpeg_normalize.replaygain: _logger.info(f"Normalized file written to {self.output_file}") ``` -------------------------------- ### Generate API Docs with pdoc Source: https://github.com/slhck/ffmpeg-normalize/blob/master/DEVELOPERS.md Use pdoc to generate API documentation. Specify the output directory and the source path. ```bash pdoc -d google -o docs-api ./src/ffmpeg_normalize ``` -------------------------------- ### Get Loudness Statistics Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/api/ffmpeg_normalize.html Retrieves loudness statistics for the audio stream, including input/output file information and EBU pass 1 and 2 results. ```python def get_stats(self) -> LoudnessStatisticsWithMetadata: """ Return loudness statistics for the stream. Returns: dict: A dictionary containing the loudness statistics. """ stats: LoudnessStatisticsWithMetadata = { "input_file": self.media_file.input_file, "output_file": self.media_file.output_file, "stream_id": self.stream_id, "ebu_pass1": self.loudness_statistics["ebu_pass1"], "ebu_pass2": self.loudness_statistics["ebu_pass2"], "mean": self.loudness_statistics["mean"], "max": self.loudness_statistics["max"], } return stats ``` -------------------------------- ### Run ReplayGain Process Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Executes the ReplayGain process for a given media file. It attempts to use second-pass EBU statistics for accuracy, falling back to first-pass statistics if necessary. Logs warnings or errors if statistics are unavailable. ```python def _run_replaygain(self) -> None: """ Run the replaygain process for this file. """ _logger.debug(f"Running replaygain for {self.input_file}") # get the audio streams audio_streams = list(self.streams["audio"].values()) # Attempt to use EBU pass 2 statistics, which account for pre-filters. # These are populated by the main second pass if it runs (not a dry run) # and normalization_type is 'ebu'. loudness_stats_source = "ebu_pass2" loudnorm_stats = audio_streams[0].loudness_statistics.get("ebu_pass2") if loudnorm_stats is None: _logger.warning( "ReplayGain: Second pass EBU statistics (ebu_pass2) not found. " "Falling back to first pass EBU statistics (ebu_pass1). " "This may not account for pre-filters if any are used." ) loudness_stats_source = "ebu_pass1" loudnorm_stats = audio_streams[0].loudness_statistics.get("ebu_pass1") if loudnorm_stats is None: _logger.error( f"ReplayGain: No loudness statistics available from {loudness_stats_source} (and fallback) for stream 0. " "Cannot calculate ReplayGain tags." ) return _logger.debug( f"Using statistics from {loudness_stats_source} for ReplayGain calculation." ) # apply the replaygain tag from the first audio stream (to all audio streams) if len(audio_streams) > 1: _logger.warning( f"Your input file has {len(audio_streams)} audio streams. " "Only the first audio stream's replaygain tag will be applied. " "All audio streams will receive the same tag." ) target_level = self.ffmpeg_normalize.target_level # Use 'input_i' and 'input_tp' from the chosen stats. # For ebu_pass2, these are measurements *after* pre-filter but *before* loudnorm adjustment. input_i = loudnorm_stats.get("input_i") input_tp = loudnorm_stats.get("input_tp") if input_i is None or input_tp is None: _logger.error( ``` -------------------------------- ### Conservative RMS Target Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/album-batch-normalization.md To mitigate clipping with RMS normalization, use a more conservative target level, such as -23 dB, leaving more headroom. This example encodes to FLAC. ```bash ffmpeg-normalize album/*.flac --batch -nt rms -t -23 -c:a flac # More conservative ``` -------------------------------- ### Encode Audio and Set Container Format Source: https://context7.com/slhck/ffmpeg-normalize/llms.txt Specify audio codec, bitrate, and container format for the output file. Use -ext to set the output file extension. ```bash # Encode output as AAC at 256 kbps inside an MP4 container ffmpeg-normalize input.mp4 -c:a aac -b:a 256k -ext mp4 -o output.mp4 ``` ```bash # Encode as Opus at 128 kbps, mono, 48 kHz ffmpeg-normalize input.wav -c:a libopus -b:a 128k -ac 1 -ar 48000 -ext ogg -o output.ogg ``` ```bash # Preserve video (default) but disable subtitles and metadata ffmpeg-normalize input.mkv -sn -mn -o output.mkv ``` ```bash # Drop the video stream entirely ffmpeg-normalize input.mkv -vn -c:a aac -ext m4a -o output.m4a ``` ```bash # Specify output container format explicitly (bypasses extension detection) ffmpeg-normalize input.wav -ofmt mp3 -c:a libmp3lame -b:a 320k -o output.mp3 ``` -------------------------------- ### Combined Denoising and Dynamic Compression Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/audio-filters.md Combine denoising with dynamic audio compression for enhanced audio processing. This example applies `anlmdn` and `dynaudnorm` filters sequentially. ```bash ffmpeg-normalize test.wav -prf "anlmdn=s=0.001:p=0.01:m=15,dynaudnorm=p=0.3:s=15" ``` -------------------------------- ### List Available Presets Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/presets.md Use this command to view all presets that are currently available in your ffmpeg-normalize configuration. ```bash ffmpeg-normalize --list-presets ``` -------------------------------- ### Get PCM Codec String - Python Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Determines the appropriate PCM codec string based on the bit depth. Falls back to 'pcm_s16le' for unsupported bit depths. ```python if not self.bit_depth: return "pcm_s16le" elif self.bit_depth <= 8: return "pcm_s8" elif self.bit_depth in [16, 24, 32, 64]: return f"pcm_s{self.bit_depth}le" else: _logger.warning( f"Unsupported bit depth {self.bit_depth}, falling back to pcm_s16le" ) return "pcm_s16le" ``` -------------------------------- ### Check for loudnorm Filter Availability Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/advanced/faq.md Verify that the `loudnorm` filter is available in your ffmpeg installation by running `ffmpeg -filters`. Outdated or incorrect ffmpeg versions may lack this filter. ```bash ffmpeg -filters ``` -------------------------------- ### Clone the Repository Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Clone the ffmpeg-normalize repository to your local machine. Navigate into the cloned directory. ```bash git clone https://github.com/YOUR-USERNAME/ffmpeg-normalize.git cd ffmpeg-normalize ``` -------------------------------- ### Run Pre-built Docker Image Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/docker.md Use this command to run the pre-built ffmpeg-normalize Docker image from Docker Hub. It mounts the current directory to /tmp inside the container. ```bash docker run -v "$(pwd):/tmp" -it slhck/ffmpeg-normalize ``` -------------------------------- ### Initialize MediaFile Object Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Initializes a MediaFile object, setting up input/output paths and determining the output file extension. It also calls parse_streams to analyze the media file's content. ```python class MediaFile: def __init__( self, ffmpeg_normalize: FFmpegNormalize, input_file: str, output_file: str ): """ Initialize a media file for later normalization by parsing the streams. Args: ffmpeg_normalize (FFmpegNormalize): reference to overall settings input_file (str): Path to input file output_file (str): Path to output file """ self.ffmpeg_normalize = ffmpeg_normalize self.skip = False self.input_file = input_file self.output_file = output_file current_ext = os.path.splitext(output_file)[1][1:] # we need to check if it's empty, e.g. /dev/null or NUL if current_ext == "" or self.output_file == os.devnull: _logger.debug( f"Current extension is unset, or output file is a null device, using extension: {self.ffmpeg_normalize.extension}" ) self.output_ext = self.ffmpeg_normalize.extension else: _logger.debug( f"Current extension is set from output file, using extension: {current_ext}" ) self.output_ext = current_ext self.streams: StreamDict = {"audio": {}, "video": {}, "subtitle": {}} self.temp_file: Union[str, None] = None self.parse_streams() ``` -------------------------------- ### Get Audio Stream Loudness Statistics Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Retrieves a dictionary containing loudness statistics for an audio stream, including EBU pass 1 and 2, mean, and max values. ```python def get_stats(self) -> ffmpeg_normalize._streams.LoudnessStatisticsWithMetadata: """ Return loudness statistics for the stream. Returns: dict: A dictionary containing the loudness statistics. """ stats: LoudnessStatisticsWithMetadata = { "input_file": self.media_file.input_file, "output_file": self.media_file.output_file, "stream_id": self.stream_id, "ebu_pass1": self.loudness_statistics["ebu_pass1"], "ebu_pass2": self.loudness_statistics["ebu_pass2"], "mean": self.loudness_statistics["mean"], "max": self.loudness_statistics["max"], } return stats ``` -------------------------------- ### Run Loudnorm First Pass Filter Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Executes the loudnorm filter in a first pass to gather loudness statistics. This is useful for analyzing audio before normalization. It requires FFmpeg to be installed and configured. ```python opts = { "i": self.media_file.ffmpeg_normalize.target_level, "lra": self.media_file.ffmpeg_normalize.loudness_range_target, "tp": self.media_file.ffmpeg_normalize.true_peak, "offset": self.media_file.ffmpeg_normalize.offset, "print_format": "json", } if self.media_file.ffmpeg_normalize.dual_mono: opts["dual_mono"] = "true" filter_str = self._get_filter_str_with_pre_filter( "loudnorm=" + dict_to_filter_opts(opts) ) cmd = [ self.media_file.ffmpeg_normalize.ffmpeg_exe, "-hide_banner", "-y", "-i", self.media_file.input_file, "-map", f"0:{self.stream_id}", "-filter_complex", filter_str, "-vn", "-sn", "-f", "null", os.devnull, ] cmd_runner = CommandRunner() yield from cmd_runner.run_ffmpeg_command(cmd) output = cmd_runner.get_output() _logger.debug( f"Loudnorm first pass command output: {CommandRunner.prune_ffmpeg_progress_from_output(output)}" ) # only one stream self.loudness_statistics["ebu_pass1"] = next( iter(AudioStream.prune_and_parse_loudnorm_output(output).values()) ) ``` -------------------------------- ### Custom Streaming Audio Preset Configuration Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/presets.md Example of a custom preset named 'streaming-audio.json' for streaming audio. It targets EBU R128 at -14.0 LUFS and encodes to AAC at 192 kbps. ```json { "normalization-type": "ebu", "target-level": -14.0, "loudness-range-target": 7.0, "true-peak": -2.0, "audio-codec": "aac", "audio-bitrate": "192k", "progress": true } ``` -------------------------------- ### Build Docker Image Locally Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/getting-started/docker.md Build the ffmpeg-normalize Docker image locally from the downloaded repository. This creates an image tagged as 'ffmpeg-normalize'. ```bash docker build -t ffmpeg-normalize . ``` -------------------------------- ### Normalize videos using an input list file Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/file-input-output.md On Windows, create a text file listing input files (one per line) and use `--input-list` to normalize them. This method also allows for batch processing. ```bat dir /b *.mkv > filelist.txt ffmpeg-normalize --input-list filelist.txt -c:a aac -b:a 192k ``` -------------------------------- ### Custom Voice Preset Configuration Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/presets.md Example of a custom preset named 'voice.json' for voice recordings. It specifies EBU normalization, a target level of -18.0 LUFS, and uses the Opus codec. ```json { "normalization-type": "ebu", "target-level": -18.0, "loudness-range-target": 4.0, "true-peak": -3.0, "audio-codec": "libopus", "audio-bitrate": "96k", "audio-channels": 1, "progress": true, "verbose": true } ``` -------------------------------- ### Normalizing Specific Audio Streams Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/basic.md These examples demonstrate how to use the -as option to normalize only specific audio streams by their index. Use `ffmpeg -i input.mkv` to find stream indices. ```bash # Normalize only stream 1 ffmpeg-normalize input.mkv -as 1 ``` ```bash # Normalize streams 1 and 2 ffmpeg-normalize input.mkv -as 1,2 ``` -------------------------------- ### Calculate and Write ReplayGain Tags Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/api/ffmpeg_normalize.html Calculates ReplayGain track gain and peak values from loudness statistics and then writes these tags to the appropriate audio file format. ```python loudnorm_stats = audio_streams[0].loudness_statistics["ebu_pass1"] if loudnorm_stats is None: _logger.error("no loudnorm stats available in first pass stats!") return target_level = self.ffmpeg_normalize.target_level input_i = loudnorm_stats["input_i"] # Integrated loudness input_tp = loudnorm_stats["input_tp"] # True peak if input_i is None or input_tp is None: _logger.error("no input_i or input_tp available in first pass stats!") return track_gain = -(input_i - target_level) # dB track_peak = 10 ** (input_tp / 20) # linear scale _logger.debug(f"Track gain: {track_gain} dB") _logger.debug(f"Track peak: {track_peak}") self._write_replaygain_tags(track_gain, track_peak) ``` -------------------------------- ### Peak Normalization to Avoid Clipping Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/album-batch-normalization.md If RMS batch normalization might cause clipping, use peak normalization instead. This example normalizes to -1 dB peak and encodes to FLAC. ```bash ffmpeg-normalize album/*.flac --batch -nt peak -t -1 -c:a flac ``` -------------------------------- ### Python: Run First Pass Loudnorm Filter Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Executes the first pass of the loudnorm filter to gather audio statistics. Requires FFmpeg executable and configuration for target loudness. Yields progress updates. ```python def parse_loudnorm_stats(self) -> Iterator[float]: """ Run a first pass loudnorm filter to get measured data. Yields: float: The progress of the command. """ _logger.info(f"Running first pass loudnorm filter for stream {self.stream_id}") opts = { "i": self.media_file.ffmpeg_normalize.target_level, "lra": self.media_file.ffmpeg_normalize.loudness_range_target, "tp": self.media_file.ffmpeg_normalize.true_peak, "offset": self.media_file.ffmpeg_normalize.offset, "print_format": "json", } if self.media_file.ffmpeg_normalize.dual_mono: opts["dual_mono"] = "true" filter_str = self._get_filter_str_with_pre_filter( "loudnorm=" + dict_to_filter_opts(opts) ) cmd = [ self.media_file.ffmpeg_normalize.ffmpeg_exe, "-hide_banner", "-y", "-i", self.media_file.input_file, "-map", f"0:{self.stream_id}", "-filter_complex", filter_str, "-vn", "-sn", "-f", "null", os.devnull, ] cmd_runner = CommandRunner() yield from cmd_runner.run_ffmpeg_command(cmd) output = cmd_runner.get_output() _logger.debug( f"Loudnorm first pass command output: {CommandRunner.prune_ffmpeg_progress_from_output(output)}" ) # only one stream self.loudness_statistics["ebu_pass1"] = next( iter(AudioStream.prune_and_parse_loudnorm_output(output).values()) ) ``` -------------------------------- ### Get EBU Loudnorm Filter Options Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/api/ffmpeg_normalize.html Returns the second pass loudnorm filter options string for FFmpeg based on EBU R128 standards. This is used for dynamic loudness adjustment. ```python def get_second_pass_opts_ebu(self) -> str: """ Return second pass loudnorm filter options string for ffmpeg """ opts = { "linear": "false" if self.media_file.ffmpeg_normalize.dynamic else "true", "print_format": "json", } if self.media_file.ffmpeg_normalize.dual_mono: opts["dual_mono"] = "true" return "loudnorm=" + dict_to_filter_opts(opts) ``` -------------------------------- ### MediaStream Constructor Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/ffmpeg_normalize.html Initializes a new MediaStream object with details about the stream and its context. ```APIDOC ## MediaStream Constructor ### Description Create a MediaStream object. ### Parameters #### Path Parameters - **ffmpeg_normalize** (FFmpegNormalize) - Required - The FFmpegNormalize object. - **media_file** (MediaFile) - Required - The MediaFile object. - **stream_type** (Literal['audio', 'video', 'subtitle']) - Required - The type of the stream. - **stream_id** (int) - Required - The stream ID. ``` -------------------------------- ### Get Audio Filter Command Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs-api/ffmpeg_normalize.html Generates the FFmpeg filter_complex command and associated output labels for audio processing. It handles conditional normalization based on loudness statistics and target levels. ```python def _get_audio_filter_cmd(self) -> tuple[str, list[str]]: """ Return the audio filter command and output labels needed. Returns: tuple[str, list[str]]: filter_complex command and the required output labels """ filter_chains = [] output_labels = [] for audio_stream in self.streams["audio"].values(): skip_normalization = False if self.ffmpeg_normalize.lower_only: if self.ffmpeg_normalize.normalization_type == "ebu": if ( audio_stream.loudness_statistics["ebu_pass1"] is not None and audio_stream.loudness_statistics["ebu_pass1"]["input_i"] < self.ffmpeg_normalize.target_level ): skip_normalization = True elif self.ffmpeg_normalize.normalization_type == "peak": if ( audio_stream.loudness_statistics["max"] is not None and audio_stream.loudness_statistics["max"] < self.ffmpeg_normalize.target_level ): skip_normalization = True elif self.ffmpeg_normalize.normalization_type == "rms": if ( audio_stream.loudness_statistics["mean"] is not None and audio_stream.loudness_statistics["mean"] < self.ffmpeg_normalize.target_level ): skip_normalization = True if skip_normalization: _logger.warning( ``` -------------------------------- ### Test the Tool Directly Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/about/contributing.md Run the ffmpeg-normalize tool directly from the command line with specified arguments to test its functionality. ```bash uv run python -m ffmpeg_normalize [args] ``` -------------------------------- ### JSON Output of Loudness Statistics Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/normalization-options.md This is an example of the JSON output generated when checking loudness statistics with the -p -n -f flags. It includes input and output levels, LRA, and normalization type. ```json [ { "input_file": "test/test.wav", "output_file": "normalized/test.mkv", "stream_id": 0, "ebu": { "input_i": -39.77, "input_tp": -27.49, "input_lra": 2.1, "input_thresh": -49.82, "output_i": -22.15, "output_tp": -9.46, "output_lra": 2.1, "output_thresh": -32.24, "normalization_type": "dynamic", "target_offset": -0.85 }, "mean": null, "max": null } ] ``` -------------------------------- ### Run Normalization Process Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs-api/ffmpeg_normalize.html Iterates through media files, runs normalization on each, and handles errors. Optionally prints statistics to stdout. ```python def run_normalization(self) -> None: """ Run the normalization procedures """ for index, media_file in enumerate( tqdm(self.media_files, desc="File", disable=not self.progress, position=0) ): _logger.info( f"Normalizing file {media_file} ({index + 1} of {self.file_count})" ) try: media_file.run_normalization() except Exception as e: if len(self.media_files) > 1: # simply warn and do not die _logger.error( f"Error processing input file {media_file}, will " f"continue batch-processing. Error was: {e}" ) else: # raise the error so the program will exit raise e if self.print_stats: json.dump( list( chain.from_iterable( media_file.get_stats() for media_file in self.media_files ) ) ), sys.stdout, indent=4, ) print() ``` -------------------------------- ### Create an MP3 output file Source: https://github.com/slhck/ffmpeg-normalize/blob/master/docs/usage/file-input-output.md Normalize an MP3 input file and output a new MP3 file. Explicitly specify the MP3 encoder and bitrate for the output. ```bash ffmpeg-normalize input.mp3 -c:a libmp3lame -b:a 320k -o output.mp3 ```