From 5857e7be38574234bc0aa1046d7e76976828aca0 Mon Sep 17 00:00:00 2001 From: Ahmet Sait Date: Tue, 19 Nov 2024 01:55:33 +0300 Subject: [PATCH] Implement `--lower-only` --- ffmpeg_normalize/__main__.py | 15 +++++++++++++ ffmpeg_normalize/_ffmpeg_normalize.py | 3 +++ ffmpeg_normalize/_media_file.py | 32 +++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/ffmpeg_normalize/__main__.py b/ffmpeg_normalize/__main__.py index 159fdde..1e5fb0a 100644 --- a/ffmpeg_normalize/__main__.py +++ b/ffmpeg_normalize/__main__.py @@ -235,6 +235,20 @@ def create_parser() -> argparse.ArgumentParser: default=0.0, ) + group_ebu.add_argument( + "--lower-only", + action="store_true", + help=textwrap.dedent( + """\ + Whether the audio should not increase in loudness. + + If the measured loudness from the first pass is lower than the target + loudness then normalization pass will be skipped for the measure audio + source. + """ + ), + ) + group_ebu.add_argument( "--dual-mono", action="store_true", @@ -514,6 +528,7 @@ def _split_options(opts: str) -> list[str]: keep_lra_above_loudness_range_target=cli_args.keep_lra_above_loudness_range_target, true_peak=cli_args.true_peak, offset=cli_args.offset, + lower_only=cli_args.lower_only, dual_mono=cli_args.dual_mono, dynamic=cli_args.dynamic, audio_codec=cli_args.audio_codec, diff --git a/ffmpeg_normalize/_ffmpeg_normalize.py b/ffmpeg_normalize/_ffmpeg_normalize.py index a1d3caa..81511b8 100644 --- a/ffmpeg_normalize/_ffmpeg_normalize.py +++ b/ffmpeg_normalize/_ffmpeg_normalize.py @@ -58,6 +58,7 @@ class FFmpegNormalize: keep_lra_above_loudness_range_target (bool, optional): Keep input loudness range above loudness range target. Defaults to False. true_peak (float, optional): True peak. Defaults to -2.0. offset (float, optional): Offset. Defaults to 0.0. + lower_only (bool, optional): Whether the audio should not increase in loudness. Defaults to False. dual_mono (bool, optional): Dual mono. Defaults to False. dynamic (bool, optional): Dynamic. Defaults to False. audio_codec (str, optional): Audio codec. Defaults to "pcm_s16le". @@ -94,6 +95,7 @@ def __init__( keep_lra_above_loudness_range_target: bool = False, true_peak: float = -2.0, offset: float = 0.0, + lower_only: bool = False, dual_mono: bool = False, dynamic: bool = False, audio_codec: str = "pcm_s16le", @@ -164,6 +166,7 @@ def __init__( self.true_peak = check_range(true_peak, -9, 0, name="true_peak") self.offset = check_range(offset, -99, 99, name="offset") + self.lower_only = lower_only # Ensure library user is passing correct types assert isinstance(dual_mono, bool), "dual_mono must be bool" diff --git a/ffmpeg_normalize/_media_file.py b/ffmpeg_normalize/_media_file.py index 279150b..a8d5f74 100644 --- a/ffmpeg_normalize/_media_file.py +++ b/ffmpeg_normalize/_media_file.py @@ -256,6 +256,30 @@ def _get_audio_filter_cmd(self) -> tuple[str, list[str]]: output_labels = [] for audio_stream in self.streams["audio"].values(): + if self.ffmpeg_normalize.lower_only: + skip = False + 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 = 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 = 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 = True + if skip: + _logger.info(f"{self.input_file}:{audio_stream.stream_id} had measured input loudness lower than target, skipping.") + continue + if self.ffmpeg_normalize.normalization_type == "ebu": normalization_filter = audio_stream.get_second_pass_opts_ebu() else: @@ -306,8 +330,12 @@ def _second_pass(self) -> Iterator[float]: # get complex filter command audio_filter_cmd, output_labels = self._get_audio_filter_cmd() - # add input file and basic filter - cmd.extend(["-i", self.input_file, "-filter_complex", audio_filter_cmd]) + # add input file + cmd.extend(["-i", self.input_file]) + + # add basic filter + if audio_filter_cmd: + cmd.extend(["-filter_complex", audio_filter_cmd]) # map metadata, only if needed if self.ffmpeg_normalize.metadata_disable: