Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions completions/ffmpeg-normalize-shtab.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -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\`.
Expand Down
4 changes: 3 additions & 1 deletion docs/usage/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions src/ffmpeg_normalize/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion src/ffmpeg_normalize/_ffmpeg_normalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 9 additions & 2 deletions src/ffmpeg_normalize/_media_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:...``
Expand All @@ -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:
Expand Down
20 changes: 20 additions & 0 deletions tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
[
Expand Down
Loading