diff --git a/completions/ffmpeg-normalize-shtab.zsh b/completions/ffmpeg-normalize-shtab.zsh index e08c9f8..a88fba7 100644 --- a/completions/ffmpeg-normalize-shtab.zsh +++ b/completions/ffmpeg-normalize-shtab.zsh @@ -207,8 +207,11 @@ Will attempt to copy video codec by default. normalized audio stream. The tag holds an ffmpeg-command-equivalent string with the audio codec, -the bitrate, sample rate and channel count (when set), plus the -normalization filter including the measured loudnorm values. +the bitrate, sample rate and channel count (when set), the normalization +filter including the measured loudnorm values, and any +\`-e\`\/\`--extra-output-options\` appended verbatim (these are not +audio-scoped, so an unrelated option passed there also ends up in the +tag). Extra input options are demuxer settings and are not included. This is a Matroska\/WebM tag convention\; other containers may store it differently. Has no effect together with \`--metadata-disable\`. diff --git a/docs/usage/cli-options.md b/docs/usage/cli-options.md index d60401b..b151ce4 100644 --- a/docs/usage/cli-options.md +++ b/docs/usage/cli-options.md @@ -320,12 +320,14 @@ Do not write chapters to output Write the effective encoder settings as an `ENCODER_SETTINGS` tag on each normalized audio stream. -The tag holds an ffmpeg-command-equivalent string with the audio codec, the bitrate, sample rate and channel count (when set), plus the normalization filter including the measured loudnorm values, for example: +The tag holds an ffmpeg-command-equivalent string with the audio codec, the bitrate, sample rate and channel count (when set), the normalization filter including the measured loudnorm values, and any `-e`/`--extra-output-options` appended verbatim, for example: ``` -c:a libopus -b:a 128000 -af loudnorm=i=-23.0:lra=7.0:tp=-2.0:offset=-0.1:measured_i=-19.4:measured_lra=6.1:measured_tp=-3.2:measured_thresh=-29.6:linear=true ``` +Extra output options are not audio-scoped, so an unrelated option passed there will also appear in the tag. Extra input options are demuxer settings and are not included. + This is a Matroska/WebM tag convention; other containers may store it differently. It has no effect together with `--metadata-disable`. ## Input/Output Format diff --git a/src/ffmpeg_normalize/__main__.py b/src/ffmpeg_normalize/__main__.py index 9b65ef9..41678e2 100644 --- a/src/ffmpeg_normalize/__main__.py +++ b/src/ffmpeg_normalize/__main__.py @@ -571,8 +571,11 @@ def create_parser() -> argparse.ArgumentParser: normalized audio stream. The tag holds an ffmpeg-command-equivalent string with the audio codec, - the bitrate, sample rate and channel count (when set), plus the - normalization filter including the measured loudnorm values. + the bitrate, sample rate and channel count (when set), the normalization + filter including the measured loudnorm values, and any + `-e`/`--extra-output-options` appended verbatim (these are not + audio-scoped, so an unrelated option passed there also ends up in the + tag). Extra input options are demuxer settings and are not included. This is a Matroska/WebM tag convention; other containers may store it differently. Has no effect together with `--metadata-disable`. diff --git a/src/ffmpeg_normalize/_ffmpeg_normalize.py b/src/ffmpeg_normalize/_ffmpeg_normalize.py index 92551dd..ba41aab 100644 --- a/src/ffmpeg_normalize/_ffmpeg_normalize.py +++ b/src/ffmpeg_normalize/_ffmpeg_normalize.py @@ -82,7 +82,7 @@ class FFmpegNormalize: subtitle_disable (bool, optional): Disable subtitles. Defaults to False. metadata_disable (bool, optional): Disable metadata. Defaults to False. chapters_disable (bool, optional): Disable chapters. Defaults to False. - write_encoder_settings (bool, optional): Write the effective encoder settings (codec, bitrate, sample rate, channels and the normalization filter including the measured loudnorm values) as an ENCODER_SETTINGS tag on each normalized audio stream. Defaults to False. + write_encoder_settings (bool, optional): Write the effective encoder settings (codec, bitrate, sample rate, channels, the normalization filter including the measured loudnorm values, and any extra_output_options) as an ENCODER_SETTINGS tag on each normalized audio stream. Defaults to False. extra_input_options (list, optional): Extra input options. Defaults to None. extra_output_options (list, optional): Extra output options. Defaults to None. output_format (str, optional): Output format. Defaults to None. diff --git a/src/ffmpeg_normalize/_media_file.py b/src/ffmpeg_normalize/_media_file.py index 7069f92..73e8eb7 100644 --- a/src/ffmpeg_normalize/_media_file.py +++ b/src/ffmpeg_normalize/_media_file.py @@ -874,13 +874,18 @@ def _get_encoder_settings( Return an ffmpeg-command-equivalent description of how a single audio stream is encoded in the second pass: the codec, then the bitrate, sample rate and channel count when set, followed by the effective - filter chain (which includes the loudnorm measured values). + filter chain (which includes the loudnorm measured values) and finally + any ``--extra-output-options``. Written as the ``ENCODER_SETTINGS`` stream tag when ``write_encoder_settings`` is enabled. The filter chain comes from the cache populated by :meth:`_get_audio_filter_cmd`, which must run first. The loudnorm ``print_format`` option is dropped, as it only controls - first-pass diagnostics and not the normalization itself. + first-pass diagnostics and not the normalization itself. Extra output + options are appended verbatim: they commonly carry encoder tuning + (``-q:a``, ``-compression_level``, ``-application`` ...), but are not + audio-scoped, so an unrelated option passed there will also appear here. + Extra *input* options are demuxer settings and are deliberately omitted. Returns: str: e.g. ``-c:a libopus -b:a 128000 -af loudnorm=i=-23.0:...`` @@ -897,6 +902,8 @@ def _get_encoder_settings( if filter_chain: af = re.sub(r":print_format=\w+", "", ",".join(filter_chain)) args += ["-af", af] + if self.ffmpeg_normalize.extra_output_options: + args += self.ffmpeg_normalize.extra_output_options return shlex.join(args) def _get_audio_codec(self) -> str | None: diff --git a/tests/test_all.py b/tests/test_all.py index cec86ee..966a48a 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -1031,6 +1031,26 @@ def test_write_encoder_settings_peak(self): assert settings.startswith("-c:a ") assert "-af volume=" in settings + def test_write_encoder_settings_extra_output_options(self): + ffmpeg_normalize_call( + [ + "tests/test.mp3", + "-o", + "normalized/test.mkv", + "-c:a", + "aac", + "-e", + '[ "-cutoff", "15000" ]', + "--write-encoder-settings", + ] + ) + assert os.path.isfile("normalized/test.mkv") + tags = _get_stream_info("normalized/test.mkv")[0]["tags"] + settings = tags.get("ENCODER_SETTINGS") or tags.get("encoder_settings") + assert settings is not None + # extra output options are appended verbatim after the filter chain + assert settings.endswith("-cutoff 15000") + def test_write_encoder_settings_metadata_disable(self): _, stderr = ffmpeg_normalize_call( [