From 7730205a0c20a17508de9f1d3c2d54a1e1827453 Mon Sep 17 00:00:00 2001 From: Ahmet Sait Date: Tue, 19 Nov 2024 17:46:01 +0300 Subject: [PATCH] Implement `--lower-only` --- ffmpeg_normalize/__main__.py | 15 +++++++++++++ ffmpeg_normalize/_ffmpeg_normalize.py | 3 +++ ffmpeg_normalize/_media_file.py | 31 ++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/ffmpeg_normalize/__main__.py b/ffmpeg_normalize/__main__.py index 159fdde..d747064 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 measured 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 044acd6..67ae5f8 100644 --- a/ffmpeg_normalize/_ffmpeg_normalize.py +++ b/ffmpeg_normalize/_ffmpeg_normalize.py @@ -60,6 +60,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". @@ -96,6 +97,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", @@ -166,6 +168,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 e844ddb..5e3ddf7 100644 --- a/ffmpeg_normalize/_media_file.py +++ b/ffmpeg_normalize/_media_file.py @@ -251,10 +251,35 @@ def _get_audio_filter_cmd(self) -> tuple[str, list[str]]: output_labels = [] for audio_stream in self.streams["audio"].values(): - if self.ffmpeg_normalize.normalization_type == "ebu": - normalization_filter = audio_stream.get_second_pass_opts_ebu() + 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.info(f"Stream {audio_stream.stream_id} had measured input loudness lower than target, skipping normalization.") + normalization_filter = "acopy" else: - normalization_filter = audio_stream.get_second_pass_opts_peakrms() + if self.ffmpeg_normalize.normalization_type == "ebu": + normalization_filter = audio_stream.get_second_pass_opts_ebu() + else: + normalization_filter = audio_stream.get_second_pass_opts_peakrms() input_label = f"[0:{audio_stream.stream_id}]" output_label = f"[norm{audio_stream.stream_id}]"