ffmpeg-normalize 1.29.2__tar.gz → 1.31.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/CHANGELOG.md +38 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/PKG-INFO +122 -12
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/README.md +80 -8
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/__main__.py +31 -3
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/_cmd_utils.py +3 -3
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/_ffmpeg_normalize.py +11 -2
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/_media_file.py +49 -20
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/_streams.py +79 -92
- ffmpeg_normalize-1.31.0/ffmpeg_normalize/_version.py +1 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/PKG-INFO +121 -11
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/setup.py +3 -3
- ffmpeg-normalize-1.29.2/ffmpeg_normalize/_version.py +0 -1
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/LICENSE +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/__init__.py +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/_errors.py +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/_logger.py +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize/py.typed +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/SOURCES.txt +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/dependency_links.txt +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/entry_points.txt +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/not-zip-safe +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/requires.txt +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/top_level.txt +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/setup.cfg +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/test/out.mp4 +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/test/test.mp4 +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/test/test.py +0 -0
- {ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/test/test.wav +0 -0
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.31.0 (2024-12-15)
|
|
5
|
+
|
|
6
|
+
* Update docs and completions.
|
|
7
|
+
|
|
8
|
+
* Implement `--auto-lower-loudness-target`
|
|
9
|
+
|
|
10
|
+
* Fix deprecations and mypy --strict errors.
|
|
11
|
+
|
|
12
|
+
* Feat: add completions.
|
|
13
|
+
|
|
14
|
+
* Docs: update explainer.
|
|
15
|
+
|
|
16
|
+
* Docs: update docs to include lower-only.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## v1.30.0 (2024-11-22)
|
|
20
|
+
|
|
21
|
+
* Change lower-only message to warning.
|
|
22
|
+
|
|
23
|
+
* Make setup name PEP 625 compliant.
|
|
24
|
+
|
|
25
|
+
* Docs: add @ahmetsait as a contributor.
|
|
26
|
+
|
|
27
|
+
* Implement `--lower-only`
|
|
28
|
+
|
|
29
|
+
* Fix: `--print-stats` only outputs the last stream.
|
|
30
|
+
|
|
31
|
+
* More robust `loudnorm` output parsing.
|
|
32
|
+
|
|
33
|
+
* Remove unnecessary conversions.
|
|
34
|
+
|
|
35
|
+
* Update .editorconfig.
|
|
36
|
+
|
|
37
|
+
* Remove python 3.8, add python 3.12, 3.13.
|
|
38
|
+
|
|
39
|
+
* Add README on file size.
|
|
40
|
+
|
|
41
|
+
|
|
4
42
|
## v1.29.2 (2024-11-18)
|
|
5
43
|
|
|
6
44
|
* Fix: show percentage with two decimal digits in progress.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
|
-
Name:
|
|
3
|
-
Version: 1.
|
|
2
|
+
Name: ffmpeg_normalize
|
|
3
|
+
Version: 1.31.0
|
|
4
4
|
Summary: Normalize audio via ffmpeg
|
|
5
5
|
Home-page: https://github.com/slhck/ffmpeg-normalize
|
|
6
6
|
Author: Werner Robitza
|
|
@@ -15,12 +15,12 @@ Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
|
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Classifier: Natural Language :: English
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ License-File: LICENSE
|
|
|
31
31
|

|
|
32
32
|
|
|
33
33
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
34
|
-
[](#contributors-)
|
|
35
35
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
36
36
|
|
|
37
37
|
A utility for batch-normalizing audio using ffmpeg.
|
|
@@ -54,6 +54,7 @@ Read on for more info.
|
|
|
54
54
|
- [Requirements](#requirements)
|
|
55
55
|
- [ffmpeg](#ffmpeg)
|
|
56
56
|
- [Installation](#installation)
|
|
57
|
+
- [Shell Completions](#shell-completions)
|
|
57
58
|
- [Usage with Docker](#usage-with-docker)
|
|
58
59
|
- [High LeveL Introduction](#high-level-introduction)
|
|
59
60
|
- [Basic Usage](#basic-usage)
|
|
@@ -69,6 +70,7 @@ Read on for more info.
|
|
|
69
70
|
- [Environment Variables](#environment-variables)
|
|
70
71
|
- [API](#api)
|
|
71
72
|
- [FAQ](#faq)
|
|
73
|
+
- [My output file is too large?](#my-output-file-is-too-large)
|
|
72
74
|
- [What options should I choose for the EBU R128 filter? What is linear and dynamic mode?](#what-options-should-i-choose-for-the-ebu-r128-filter-what-is-linear-and-dynamic-mode)
|
|
73
75
|
- [The program doesn't work because the "loudnorm" filter can't be found](#the-program-doesnt-work-because-the-loudnorm-filter-cant-be-found)
|
|
74
76
|
- [Should I use this to normalize my music collection?](#should-i-use-this-to-normalize-my-music-collection)
|
|
@@ -88,7 +90,7 @@ Read on for more info.
|
|
|
88
90
|
|
|
89
91
|
## Requirements
|
|
90
92
|
|
|
91
|
-
You need Python 3.
|
|
93
|
+
You need Python 3.9 or higher, and ffmpeg.
|
|
92
94
|
|
|
93
95
|
### ffmpeg
|
|
94
96
|
|
|
@@ -129,6 +131,54 @@ Or download this repository, then run `pip3 install .`.
|
|
|
129
131
|
|
|
130
132
|
To later upgrade to the latest version, run `pip3 install --upgrade ffmpeg-normalize`.
|
|
131
133
|
|
|
134
|
+
### Shell Completions
|
|
135
|
+
|
|
136
|
+
This tool provides shell completions for bash and zsh. To install them:
|
|
137
|
+
|
|
138
|
+
<!--
|
|
139
|
+
Note to self: Generate the shtab ones with:
|
|
140
|
+
|
|
141
|
+
shtab --shell=bash -u ffmpeg_normalize.__main__.create_parser > completions/ffmpeg-normalize-shtab.bash
|
|
142
|
+
shtab --shell=zsh -u ffmpeg_normalize.__main__.create_parser > completions/ffmpeg-normalize-shtab.zsh
|
|
143
|
+
|
|
144
|
+
but these are not properly working yet.
|
|
145
|
+
-->
|
|
146
|
+
|
|
147
|
+
#### Bash
|
|
148
|
+
|
|
149
|
+
If you have [`bash-completion`](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to the `/usr/local/etc/bash_completion.d` directory.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize-completion.bash \
|
|
153
|
+
-o /usr/local/etc/bash_completion.d/ffmpeg-normalize
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Without bash-completion, you can manually install the completion script:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# create completions directory if it doesn't exist
|
|
160
|
+
mkdir -p ~/.bash_completions.d
|
|
161
|
+
|
|
162
|
+
# download and install completion script
|
|
163
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize-completion.bash \
|
|
164
|
+
-o ~/.bash_completions.d/ffmpeg-normalize
|
|
165
|
+
|
|
166
|
+
# source it in your ~/.bashrc
|
|
167
|
+
echo 'source ~/.bash_completions.d/ffmpeg-normalize' >> ~/.bashrc
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Zsh
|
|
171
|
+
|
|
172
|
+
Download the completion script and place it in the default `site-functions` directory:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize.zsh \
|
|
176
|
+
-o /usr/local/share/zsh/site-functions/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
You may choose any other directory that is in your `$FPATH` variable.
|
|
180
|
+
Make sure your `.zshrc` file contains `autoload -Uz compinit && compinit`.
|
|
181
|
+
|
|
132
182
|
## Usage with Docker
|
|
133
183
|
|
|
134
184
|
You can use the pre-built image from Docker Hub:
|
|
@@ -280,10 +330,7 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
280
330
|
|
|
281
331
|
- `--keep-lra-above-loudness-range-target`: Keep input loudness range above loudness range target.
|
|
282
332
|
|
|
283
|
-
|
|
284
|
-
- keep input loudness range target above `LOUDNESS_RANGE_TARGET`.
|
|
285
|
-
|
|
286
|
-
as alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
333
|
+
Can be used as an alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
287
334
|
|
|
288
335
|
- `-tp TRUE_PEAK, --true-peak TRUE_PEAK`: EBU Maximum True Peak in dBTP (default: -2.0).
|
|
289
336
|
|
|
@@ -295,6 +342,16 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
295
342
|
|
|
296
343
|
Range is -99.0 - +99.0.
|
|
297
344
|
|
|
345
|
+
- `--lower-only`: Whether the audio should not increase in loudness.
|
|
346
|
+
|
|
347
|
+
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.
|
|
348
|
+
|
|
349
|
+
- `--auto-lower-loudness-target`: Automatically lower EBU Integrated Loudness Target.
|
|
350
|
+
|
|
351
|
+
Automatically lower EBU Integrated Loudness Target to prevent falling back to dynamic filtering.
|
|
352
|
+
|
|
353
|
+
Makes sure target loudness is lower than measured loudness minus peak loudness (input_i - input_tp) by a small amount.
|
|
354
|
+
|
|
298
355
|
- `--dual-mono`: Treat mono input files as "dual-mono".
|
|
299
356
|
|
|
300
357
|
If a mono file is intended for playback on a stereo system, its EBU R128 measurement will be perceptually incorrect. If set, this option will compensate for this effect. Multi-channel input files are not affected by this option.
|
|
@@ -303,7 +360,7 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
303
360
|
|
|
304
361
|
Instead of applying linear EBU R128 normalization, choose a dynamic normalization. This is not usually recommended.
|
|
305
362
|
|
|
306
|
-
Dynamic mode will automatically change the sample rate to 192 kHz. Use
|
|
363
|
+
Dynamic mode will automatically change the sample rate to 192 kHz. Use `-ar`/`--sample-rate` to specify a different output sample rate.
|
|
307
364
|
|
|
308
365
|
### Audio Encoding
|
|
309
366
|
|
|
@@ -405,6 +462,16 @@ For more information see the [API documentation](https://htmlpreview.github.io/?
|
|
|
405
462
|
|
|
406
463
|
## FAQ
|
|
407
464
|
|
|
465
|
+
### My output file is too large?
|
|
466
|
+
|
|
467
|
+
This is because the default output codec is PCM, which is uncompressed. If you want to reduce the file size, you can specify an audio codec with `-c:a` (e.g., `-c:a aac` for ffmpeg's built-in AAC encoder), and optionally a bitrate with `-b:a`.
|
|
468
|
+
|
|
469
|
+
For example:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
ffmpeg-normalize input.wav -o output.m4a -c:a aac -b:a 192k
|
|
473
|
+
```
|
|
474
|
+
|
|
408
475
|
### What options should I choose for the EBU R128 filter? What is linear and dynamic mode?
|
|
409
476
|
|
|
410
477
|
EBU R128 is a method for normalizing audio loudness across different tracks or programs. It works by analyzing the audio content and adjusting it to meet specific loudness targets. The main components are:
|
|
@@ -429,7 +496,11 @@ For most cases, linear mode is recommended. Dynamic mode should only be used whe
|
|
|
429
496
|
|
|
430
497
|
* When the required gain adjustment to meet the integrated loudness target would result in the true peak exceeding the specified true peak limit. This is because linear processing alone cannot reduce peaks without affecting the entire signal. For example, if a file needs to be amplified by 6 dB to reach the target integrated loudness, but doing so would push the true peak above the specified limit, the filter might switch to dynamic mode to handle this situation. If your content allows for it, you can increase the true peak target to give more headroom for linear processing. If you're consistently running into true peak issues, you might also consider lowering your target integrated loudness level.
|
|
431
498
|
|
|
432
|
-
At this time, the `loudnorm` filter in ffmpeg does not provide a way to force linear mode when the input loudness range exceeds the target or when the true peak would be exceeded.
|
|
499
|
+
At this time, the `loudnorm` filter in ffmpeg does not provide a way to force linear mode when the input loudness range exceeds the target or when the true peak would be exceeded. There are some options to mitigate this:
|
|
500
|
+
|
|
501
|
+
- The `--keep-lra-above-loudness-range-target` option can be used to keep the input loudness range above the specified target, but it will not force linear mode in all cases.
|
|
502
|
+
- Similarly, the `--keep-loudness-range-target` option can be used to keep the input loudness range target.
|
|
503
|
+
- The `--lower-only` option can be used to skip the normalization pass completely if the measured loudness is lower than the target loudness.
|
|
433
504
|
|
|
434
505
|
### The program doesn't work because the "loudnorm" filter can't be found
|
|
435
506
|
|
|
@@ -548,6 +619,7 @@ If you found this program useful and feel like giving back, feel free to send a
|
|
|
548
619
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/psavva"><img src="https://avatars.githubusercontent.com/u/1454758?v=4?s=100" width="100px;" alt="Panayiotis Savva"/><br /><sub><b>Panayiotis Savva</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=psavva" title="Code">💻</a></td>
|
|
549
620
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HighMans"><img src="https://avatars.githubusercontent.com/u/42877729?v=4?s=100" width="100px;" alt="HighMans"/><br /><sub><b>HighMans</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=HighMans" title="Code">💻</a></td>
|
|
550
621
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kanjieater"><img src="https://avatars.githubusercontent.com/u/32607317?v=4?s=100" width="100px;" alt="kanjieater"/><br /><sub><b>kanjieater</b></sub></a><br /><a href="#ideas-kanjieater" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
622
|
+
<td align="center" valign="top" width="14.28%"><a href="https://ahmetsait.com/"><img src="https://avatars.githubusercontent.com/u/8372246?v=4?s=100" width="100px;" alt="Ahmet Sait"/><br /><sub><b>Ahmet Sait</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=ahmetsait" title="Code">💻</a></td>
|
|
551
623
|
</tr>
|
|
552
624
|
</tbody>
|
|
553
625
|
<tfoot>
|
|
@@ -595,6 +667,44 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
595
667
|
# Changelog
|
|
596
668
|
|
|
597
669
|
|
|
670
|
+
## v1.31.0 (2024-12-15)
|
|
671
|
+
|
|
672
|
+
* Update docs and completions.
|
|
673
|
+
|
|
674
|
+
* Implement `--auto-lower-loudness-target`
|
|
675
|
+
|
|
676
|
+
* Fix deprecations and mypy --strict errors.
|
|
677
|
+
|
|
678
|
+
* Feat: add completions.
|
|
679
|
+
|
|
680
|
+
* Docs: update explainer.
|
|
681
|
+
|
|
682
|
+
* Docs: update docs to include lower-only.
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
## v1.30.0 (2024-11-22)
|
|
686
|
+
|
|
687
|
+
* Change lower-only message to warning.
|
|
688
|
+
|
|
689
|
+
* Make setup name PEP 625 compliant.
|
|
690
|
+
|
|
691
|
+
* Docs: add @ahmetsait as a contributor.
|
|
692
|
+
|
|
693
|
+
* Implement `--lower-only`
|
|
694
|
+
|
|
695
|
+
* Fix: `--print-stats` only outputs the last stream.
|
|
696
|
+
|
|
697
|
+
* More robust `loudnorm` output parsing.
|
|
698
|
+
|
|
699
|
+
* Remove unnecessary conversions.
|
|
700
|
+
|
|
701
|
+
* Update .editorconfig.
|
|
702
|
+
|
|
703
|
+
* Remove python 3.8, add python 3.12, 3.13.
|
|
704
|
+
|
|
705
|
+
* Add README on file size.
|
|
706
|
+
|
|
707
|
+
|
|
598
708
|
## v1.29.2 (2024-11-18)
|
|
599
709
|
|
|
600
710
|
* Fix: show percentage with two decimal digits in progress.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
8
|
-
[](#contributors-)
|
|
9
9
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
10
10
|
|
|
11
11
|
A utility for batch-normalizing audio using ffmpeg.
|
|
@@ -28,6 +28,7 @@ Read on for more info.
|
|
|
28
28
|
- [Requirements](#requirements)
|
|
29
29
|
- [ffmpeg](#ffmpeg)
|
|
30
30
|
- [Installation](#installation)
|
|
31
|
+
- [Shell Completions](#shell-completions)
|
|
31
32
|
- [Usage with Docker](#usage-with-docker)
|
|
32
33
|
- [High LeveL Introduction](#high-level-introduction)
|
|
33
34
|
- [Basic Usage](#basic-usage)
|
|
@@ -43,6 +44,7 @@ Read on for more info.
|
|
|
43
44
|
- [Environment Variables](#environment-variables)
|
|
44
45
|
- [API](#api)
|
|
45
46
|
- [FAQ](#faq)
|
|
47
|
+
- [My output file is too large?](#my-output-file-is-too-large)
|
|
46
48
|
- [What options should I choose for the EBU R128 filter? What is linear and dynamic mode?](#what-options-should-i-choose-for-the-ebu-r128-filter-what-is-linear-and-dynamic-mode)
|
|
47
49
|
- [The program doesn't work because the "loudnorm" filter can't be found](#the-program-doesnt-work-because-the-loudnorm-filter-cant-be-found)
|
|
48
50
|
- [Should I use this to normalize my music collection?](#should-i-use-this-to-normalize-my-music-collection)
|
|
@@ -62,7 +64,7 @@ Read on for more info.
|
|
|
62
64
|
|
|
63
65
|
## Requirements
|
|
64
66
|
|
|
65
|
-
You need Python 3.
|
|
67
|
+
You need Python 3.9 or higher, and ffmpeg.
|
|
66
68
|
|
|
67
69
|
### ffmpeg
|
|
68
70
|
|
|
@@ -103,6 +105,54 @@ Or download this repository, then run `pip3 install .`.
|
|
|
103
105
|
|
|
104
106
|
To later upgrade to the latest version, run `pip3 install --upgrade ffmpeg-normalize`.
|
|
105
107
|
|
|
108
|
+
### Shell Completions
|
|
109
|
+
|
|
110
|
+
This tool provides shell completions for bash and zsh. To install them:
|
|
111
|
+
|
|
112
|
+
<!--
|
|
113
|
+
Note to self: Generate the shtab ones with:
|
|
114
|
+
|
|
115
|
+
shtab --shell=bash -u ffmpeg_normalize.__main__.create_parser > completions/ffmpeg-normalize-shtab.bash
|
|
116
|
+
shtab --shell=zsh -u ffmpeg_normalize.__main__.create_parser > completions/ffmpeg-normalize-shtab.zsh
|
|
117
|
+
|
|
118
|
+
but these are not properly working yet.
|
|
119
|
+
-->
|
|
120
|
+
|
|
121
|
+
#### Bash
|
|
122
|
+
|
|
123
|
+
If you have [`bash-completion`](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to the `/usr/local/etc/bash_completion.d` directory.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize-completion.bash \
|
|
127
|
+
-o /usr/local/etc/bash_completion.d/ffmpeg-normalize
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Without bash-completion, you can manually install the completion script:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# create completions directory if it doesn't exist
|
|
134
|
+
mkdir -p ~/.bash_completions.d
|
|
135
|
+
|
|
136
|
+
# download and install completion script
|
|
137
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize-completion.bash \
|
|
138
|
+
-o ~/.bash_completions.d/ffmpeg-normalize
|
|
139
|
+
|
|
140
|
+
# source it in your ~/.bashrc
|
|
141
|
+
echo 'source ~/.bash_completions.d/ffmpeg-normalize' >> ~/.bashrc
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Zsh
|
|
145
|
+
|
|
146
|
+
Download the completion script and place it in the default `site-functions` directory:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize.zsh \
|
|
150
|
+
-o /usr/local/share/zsh/site-functions/
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
You may choose any other directory that is in your `$FPATH` variable.
|
|
154
|
+
Make sure your `.zshrc` file contains `autoload -Uz compinit && compinit`.
|
|
155
|
+
|
|
106
156
|
## Usage with Docker
|
|
107
157
|
|
|
108
158
|
You can use the pre-built image from Docker Hub:
|
|
@@ -254,10 +304,7 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
254
304
|
|
|
255
305
|
- `--keep-lra-above-loudness-range-target`: Keep input loudness range above loudness range target.
|
|
256
306
|
|
|
257
|
-
|
|
258
|
-
- keep input loudness range target above `LOUDNESS_RANGE_TARGET`.
|
|
259
|
-
|
|
260
|
-
as alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
307
|
+
Can be used as an alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
261
308
|
|
|
262
309
|
- `-tp TRUE_PEAK, --true-peak TRUE_PEAK`: EBU Maximum True Peak in dBTP (default: -2.0).
|
|
263
310
|
|
|
@@ -269,6 +316,16 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
269
316
|
|
|
270
317
|
Range is -99.0 - +99.0.
|
|
271
318
|
|
|
319
|
+
- `--lower-only`: Whether the audio should not increase in loudness.
|
|
320
|
+
|
|
321
|
+
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.
|
|
322
|
+
|
|
323
|
+
- `--auto-lower-loudness-target`: Automatically lower EBU Integrated Loudness Target.
|
|
324
|
+
|
|
325
|
+
Automatically lower EBU Integrated Loudness Target to prevent falling back to dynamic filtering.
|
|
326
|
+
|
|
327
|
+
Makes sure target loudness is lower than measured loudness minus peak loudness (input_i - input_tp) by a small amount.
|
|
328
|
+
|
|
272
329
|
- `--dual-mono`: Treat mono input files as "dual-mono".
|
|
273
330
|
|
|
274
331
|
If a mono file is intended for playback on a stereo system, its EBU R128 measurement will be perceptually incorrect. If set, this option will compensate for this effect. Multi-channel input files are not affected by this option.
|
|
@@ -277,7 +334,7 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
277
334
|
|
|
278
335
|
Instead of applying linear EBU R128 normalization, choose a dynamic normalization. This is not usually recommended.
|
|
279
336
|
|
|
280
|
-
Dynamic mode will automatically change the sample rate to 192 kHz. Use
|
|
337
|
+
Dynamic mode will automatically change the sample rate to 192 kHz. Use `-ar`/`--sample-rate` to specify a different output sample rate.
|
|
281
338
|
|
|
282
339
|
### Audio Encoding
|
|
283
340
|
|
|
@@ -379,6 +436,16 @@ For more information see the [API documentation](https://htmlpreview.github.io/?
|
|
|
379
436
|
|
|
380
437
|
## FAQ
|
|
381
438
|
|
|
439
|
+
### My output file is too large?
|
|
440
|
+
|
|
441
|
+
This is because the default output codec is PCM, which is uncompressed. If you want to reduce the file size, you can specify an audio codec with `-c:a` (e.g., `-c:a aac` for ffmpeg's built-in AAC encoder), and optionally a bitrate with `-b:a`.
|
|
442
|
+
|
|
443
|
+
For example:
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
ffmpeg-normalize input.wav -o output.m4a -c:a aac -b:a 192k
|
|
447
|
+
```
|
|
448
|
+
|
|
382
449
|
### What options should I choose for the EBU R128 filter? What is linear and dynamic mode?
|
|
383
450
|
|
|
384
451
|
EBU R128 is a method for normalizing audio loudness across different tracks or programs. It works by analyzing the audio content and adjusting it to meet specific loudness targets. The main components are:
|
|
@@ -403,7 +470,11 @@ For most cases, linear mode is recommended. Dynamic mode should only be used whe
|
|
|
403
470
|
|
|
404
471
|
* When the required gain adjustment to meet the integrated loudness target would result in the true peak exceeding the specified true peak limit. This is because linear processing alone cannot reduce peaks without affecting the entire signal. For example, if a file needs to be amplified by 6 dB to reach the target integrated loudness, but doing so would push the true peak above the specified limit, the filter might switch to dynamic mode to handle this situation. If your content allows for it, you can increase the true peak target to give more headroom for linear processing. If you're consistently running into true peak issues, you might also consider lowering your target integrated loudness level.
|
|
405
472
|
|
|
406
|
-
At this time, the `loudnorm` filter in ffmpeg does not provide a way to force linear mode when the input loudness range exceeds the target or when the true peak would be exceeded.
|
|
473
|
+
At this time, the `loudnorm` filter in ffmpeg does not provide a way to force linear mode when the input loudness range exceeds the target or when the true peak would be exceeded. There are some options to mitigate this:
|
|
474
|
+
|
|
475
|
+
- The `--keep-lra-above-loudness-range-target` option can be used to keep the input loudness range above the specified target, but it will not force linear mode in all cases.
|
|
476
|
+
- Similarly, the `--keep-loudness-range-target` option can be used to keep the input loudness range target.
|
|
477
|
+
- The `--lower-only` option can be used to skip the normalization pass completely if the measured loudness is lower than the target loudness.
|
|
407
478
|
|
|
408
479
|
### The program doesn't work because the "loudnorm" filter can't be found
|
|
409
480
|
|
|
@@ -522,6 +593,7 @@ If you found this program useful and feel like giving back, feel free to send a
|
|
|
522
593
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/psavva"><img src="https://avatars.githubusercontent.com/u/1454758?v=4?s=100" width="100px;" alt="Panayiotis Savva"/><br /><sub><b>Panayiotis Savva</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=psavva" title="Code">💻</a></td>
|
|
523
594
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HighMans"><img src="https://avatars.githubusercontent.com/u/42877729?v=4?s=100" width="100px;" alt="HighMans"/><br /><sub><b>HighMans</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=HighMans" title="Code">💻</a></td>
|
|
524
595
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kanjieater"><img src="https://avatars.githubusercontent.com/u/32607317?v=4?s=100" width="100px;" alt="kanjieater"/><br /><sub><b>kanjieater</b></sub></a><br /><a href="#ideas-kanjieater" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
596
|
+
<td align="center" valign="top" width="14.28%"><a href="https://ahmetsait.com/"><img src="https://avatars.githubusercontent.com/u/8372246?v=4?s=100" width="100px;" alt="Ahmet Sait"/><br /><sub><b>Ahmet Sait</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=ahmetsait" title="Code">💻</a></td>
|
|
525
597
|
</tr>
|
|
526
598
|
</tbody>
|
|
527
599
|
<tfoot>
|
|
@@ -201,9 +201,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
201
201
|
help=textwrap.dedent(
|
|
202
202
|
"""\
|
|
203
203
|
Keep input loudness range above loudness range target.
|
|
204
|
-
|
|
205
|
-
- keep input loudness range target above `LOUDNESS_RANGE_TARGET`.
|
|
206
|
-
as alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
204
|
+
Can be used as an alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
207
205
|
"""
|
|
208
206
|
),
|
|
209
207
|
)
|
|
@@ -235,6 +233,34 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
235
233
|
default=0.0,
|
|
236
234
|
)
|
|
237
235
|
|
|
236
|
+
group_ebu.add_argument(
|
|
237
|
+
"--lower-only",
|
|
238
|
+
action="store_true",
|
|
239
|
+
help=textwrap.dedent(
|
|
240
|
+
"""\
|
|
241
|
+
Whether the audio should not increase in loudness.
|
|
242
|
+
|
|
243
|
+
If the measured loudness from the first pass is lower than the target
|
|
244
|
+
loudness then normalization pass will be skipped for the measured audio
|
|
245
|
+
source.
|
|
246
|
+
"""
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
group_ebu.add_argument(
|
|
251
|
+
"--auto-lower-loudness-target",
|
|
252
|
+
action="store_true",
|
|
253
|
+
help=textwrap.dedent(
|
|
254
|
+
"""\
|
|
255
|
+
Automatically lower EBU Integrated Loudness Target to prevent falling
|
|
256
|
+
back to dynamic filtering.
|
|
257
|
+
|
|
258
|
+
Makes sure target loudness is lower than measured loudness minus peak
|
|
259
|
+
loudness (input_i - input_tp) by a small amount (0.1 LUFS).
|
|
260
|
+
"""
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
|
|
238
264
|
group_ebu.add_argument(
|
|
239
265
|
"--dual-mono",
|
|
240
266
|
action="store_true",
|
|
@@ -514,6 +540,8 @@ def main() -> None:
|
|
|
514
540
|
keep_lra_above_loudness_range_target=cli_args.keep_lra_above_loudness_range_target,
|
|
515
541
|
true_peak=cli_args.true_peak,
|
|
516
542
|
offset=cli_args.offset,
|
|
543
|
+
lower_only=cli_args.lower_only,
|
|
544
|
+
auto_lower_loudness_target=cli_args.auto_lower_loudness_target,
|
|
517
545
|
dual_mono=cli_args.dual_mono,
|
|
518
546
|
dynamic=cli_args.dynamic,
|
|
519
547
|
audio_codec=cli_args.audio_codec,
|
|
@@ -7,7 +7,7 @@ import shlex
|
|
|
7
7
|
import subprocess
|
|
8
8
|
from platform import system
|
|
9
9
|
from shutil import which
|
|
10
|
-
from typing import Iterator
|
|
10
|
+
from typing import Iterator, Any
|
|
11
11
|
|
|
12
12
|
from ffmpeg_progress_yield import FfmpegProgress
|
|
13
13
|
|
|
@@ -128,12 +128,12 @@ class CommandRunner:
|
|
|
128
128
|
return self.output
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
def dict_to_filter_opts(opts: dict[str,
|
|
131
|
+
def dict_to_filter_opts(opts: dict[str, Any]) -> str:
|
|
132
132
|
"""
|
|
133
133
|
Convert a dictionary to a ffmpeg filter option string
|
|
134
134
|
|
|
135
135
|
Args:
|
|
136
|
-
opts (dict[str,
|
|
136
|
+
opts (dict[str, Any]): Dictionary of options
|
|
137
137
|
|
|
138
138
|
Returns:
|
|
139
139
|
str: Filter option string
|
|
@@ -3,6 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
+
import sys
|
|
7
|
+
from itertools import chain
|
|
6
8
|
from typing import TYPE_CHECKING, Literal
|
|
7
9
|
|
|
8
10
|
from tqdm import tqdm
|
|
@@ -58,6 +60,8 @@ class FFmpegNormalize:
|
|
|
58
60
|
keep_lra_above_loudness_range_target (bool, optional): Keep input loudness range above loudness range target. Defaults to False.
|
|
59
61
|
true_peak (float, optional): True peak. Defaults to -2.0.
|
|
60
62
|
offset (float, optional): Offset. Defaults to 0.0.
|
|
63
|
+
lower_only (bool, optional): Whether the audio should not increase in loudness. Defaults to False.
|
|
64
|
+
auto_lower_loudness_target (bool, optional): Automatically lower EBU Integrated Loudness Target.
|
|
61
65
|
dual_mono (bool, optional): Dual mono. Defaults to False.
|
|
62
66
|
dynamic (bool, optional): Dynamic. Defaults to False.
|
|
63
67
|
audio_codec (str, optional): Audio codec. Defaults to "pcm_s16le".
|
|
@@ -94,6 +98,8 @@ class FFmpegNormalize:
|
|
|
94
98
|
keep_lra_above_loudness_range_target: bool = False,
|
|
95
99
|
true_peak: float = -2.0,
|
|
96
100
|
offset: float = 0.0,
|
|
101
|
+
lower_only: bool = False,
|
|
102
|
+
auto_lower_loudness_target: bool = False,
|
|
97
103
|
dual_mono: bool = False,
|
|
98
104
|
dynamic: bool = False,
|
|
99
105
|
audio_codec: str = "pcm_s16le",
|
|
@@ -164,6 +170,8 @@ class FFmpegNormalize:
|
|
|
164
170
|
|
|
165
171
|
self.true_peak = check_range(true_peak, -9, 0, name="true_peak")
|
|
166
172
|
self.offset = check_range(offset, -99, 99, name="offset")
|
|
173
|
+
self.lower_only = lower_only
|
|
174
|
+
self.auto_lower_loudness_target = auto_lower_loudness_target
|
|
167
175
|
|
|
168
176
|
# Ensure library user is passing correct types
|
|
169
177
|
assert isinstance(dual_mono, bool), "dual_mono must be bool"
|
|
@@ -254,5 +262,6 @@ class FFmpegNormalize:
|
|
|
254
262
|
|
|
255
263
|
_logger.info(f"Normalized file written to {media_file.output_file}")
|
|
256
264
|
|
|
257
|
-
if self.print_stats
|
|
258
|
-
|
|
265
|
+
if self.print_stats:
|
|
266
|
+
json.dump(list(chain.from_iterable(media_file.get_stats() for media_file in self.media_files)), sys.stdout, indent=4)
|
|
267
|
+
print()
|
|
@@ -6,13 +6,18 @@ import re
|
|
|
6
6
|
import shlex
|
|
7
7
|
from shutil import move, rmtree
|
|
8
8
|
from tempfile import mkdtemp
|
|
9
|
-
from typing import TYPE_CHECKING, Iterator, Literal, TypedDict
|
|
9
|
+
from typing import TYPE_CHECKING, Iterable, Iterator, Literal, TypedDict
|
|
10
10
|
|
|
11
11
|
from tqdm import tqdm
|
|
12
12
|
|
|
13
13
|
from ._cmd_utils import DUR_REGEX, NUL, CommandRunner
|
|
14
14
|
from ._errors import FFmpegNormalizeError
|
|
15
|
-
from ._streams import
|
|
15
|
+
from ._streams import (
|
|
16
|
+
AudioStream,
|
|
17
|
+
LoudnessStatisticsWithMetadata,
|
|
18
|
+
SubtitleStream,
|
|
19
|
+
VideoStream,
|
|
20
|
+
)
|
|
16
21
|
|
|
17
22
|
if TYPE_CHECKING:
|
|
18
23
|
from ffmpeg_normalize import FFmpegNormalize
|
|
@@ -240,11 +245,6 @@ class MediaFile:
|
|
|
240
245
|
for _ in fun():
|
|
241
246
|
pass
|
|
242
247
|
|
|
243
|
-
# set initial stats (for dry-runs, this is the only thing we need to do)
|
|
244
|
-
self.ffmpeg_normalize.stats = [
|
|
245
|
-
audio_stream.get_stats() for audio_stream in self.streams["audio"].values()
|
|
246
|
-
]
|
|
247
|
-
|
|
248
248
|
def _get_audio_filter_cmd(self) -> tuple[str, list[str]]:
|
|
249
249
|
"""
|
|
250
250
|
Return the audio filter command and output labels needed.
|
|
@@ -256,10 +256,40 @@ class MediaFile:
|
|
|
256
256
|
output_labels = []
|
|
257
257
|
|
|
258
258
|
for audio_stream in self.streams["audio"].values():
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
skip_normalization = False
|
|
260
|
+
if self.ffmpeg_normalize.lower_only:
|
|
261
|
+
if self.ffmpeg_normalize.normalization_type == "ebu":
|
|
262
|
+
if (
|
|
263
|
+
audio_stream.loudness_statistics["ebu_pass1"] is not None
|
|
264
|
+
and audio_stream.loudness_statistics["ebu_pass1"]["input_i"]
|
|
265
|
+
< self.ffmpeg_normalize.target_level
|
|
266
|
+
):
|
|
267
|
+
skip_normalization = True
|
|
268
|
+
elif self.ffmpeg_normalize.normalization_type == "peak":
|
|
269
|
+
if (
|
|
270
|
+
audio_stream.loudness_statistics["max"] is not None
|
|
271
|
+
and audio_stream.loudness_statistics["max"]
|
|
272
|
+
< self.ffmpeg_normalize.target_level
|
|
273
|
+
):
|
|
274
|
+
skip_normalization = True
|
|
275
|
+
elif self.ffmpeg_normalize.normalization_type == "rms":
|
|
276
|
+
if (
|
|
277
|
+
audio_stream.loudness_statistics["mean"] is not None
|
|
278
|
+
and audio_stream.loudness_statistics["mean"]
|
|
279
|
+
< self.ffmpeg_normalize.target_level
|
|
280
|
+
):
|
|
281
|
+
skip_normalization = True
|
|
282
|
+
|
|
283
|
+
if skip_normalization:
|
|
284
|
+
_logger.warning(
|
|
285
|
+
f"Stream {audio_stream.stream_id} had measured input loudness lower than target, skipping normalization."
|
|
286
|
+
)
|
|
287
|
+
normalization_filter = "acopy"
|
|
261
288
|
else:
|
|
262
|
-
|
|
289
|
+
if self.ffmpeg_normalize.normalization_type == "ebu":
|
|
290
|
+
normalization_filter = audio_stream.get_second_pass_opts_ebu()
|
|
291
|
+
else:
|
|
292
|
+
normalization_filter = audio_stream.get_second_pass_opts_peakrms()
|
|
263
293
|
|
|
264
294
|
input_label = f"[0:{audio_stream.stream_id}]"
|
|
265
295
|
output_label = f"[norm{audio_stream.stream_id}]"
|
|
@@ -421,16 +451,10 @@ class MediaFile:
|
|
|
421
451
|
# in the second pass, we do not normalize stream-by-stream, so we set the stats based on the
|
|
422
452
|
# overall output (which includes multiple loudnorm stats)
|
|
423
453
|
if self.ffmpeg_normalize.normalization_type == "ebu":
|
|
424
|
-
all_stats = AudioStream.prune_and_parse_loudnorm_output(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
audio_stream.set_second_pass_stats(all_stats[idx])
|
|
429
|
-
|
|
430
|
-
# collect all stats for the final report, again (overwrite the input)
|
|
431
|
-
self.ffmpeg_normalize.stats = [
|
|
432
|
-
audio_stream.get_stats() for audio_stream in self.streams["audio"].values()
|
|
433
|
-
]
|
|
454
|
+
all_stats = AudioStream.prune_and_parse_loudnorm_output(output)
|
|
455
|
+
for stream_id, audio_stream in self.streams["audio"].items():
|
|
456
|
+
if stream_id in all_stats:
|
|
457
|
+
audio_stream.set_second_pass_stats(all_stats[stream_id])
|
|
434
458
|
|
|
435
459
|
# warn if self.media_file.ffmpeg_normalize.dynamic == False and any of the second pass stats contain "normalization_type" == "dynamic"
|
|
436
460
|
if self.ffmpeg_normalize.dynamic is False:
|
|
@@ -446,3 +470,8 @@ class MediaFile:
|
|
|
446
470
|
)
|
|
447
471
|
|
|
448
472
|
_logger.debug("Normalization finished")
|
|
473
|
+
|
|
474
|
+
def get_stats(self) -> Iterable[LoudnessStatisticsWithMetadata]:
|
|
475
|
+
return (
|
|
476
|
+
audio_stream.get_stats() for audio_stream in self.streams["audio"].values()
|
|
477
|
+
)
|
|
@@ -15,6 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
|
|
16
16
|
_logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
|
+
_loudnorm_pattern = re.compile(r"\[Parsed_loudnorm_(\d+)")
|
|
18
19
|
|
|
19
20
|
class EbuLoudnessStatistics(TypedDict):
|
|
20
21
|
input_i: float
|
|
@@ -166,7 +167,7 @@ class AudioStream(MediaStream):
|
|
|
166
167
|
}
|
|
167
168
|
return stats
|
|
168
169
|
|
|
169
|
-
def set_second_pass_stats(self, stats: EbuLoudnessStatistics):
|
|
170
|
+
def set_second_pass_stats(self, stats: EbuLoudnessStatistics) -> None:
|
|
170
171
|
"""
|
|
171
172
|
Set the EBU loudness statistics for the second pass.
|
|
172
173
|
|
|
@@ -320,58 +321,36 @@ class AudioStream(MediaStream):
|
|
|
320
321
|
f"Loudnorm first pass command output: {CommandRunner.prune_ffmpeg_progress_from_output(output)}"
|
|
321
322
|
)
|
|
322
323
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
output, num_stats=1
|
|
326
|
-
)[0] # only one stream
|
|
327
|
-
)
|
|
324
|
+
# only one stream
|
|
325
|
+
self.loudness_statistics["ebu_pass1"] = next(iter(AudioStream.prune_and_parse_loudnorm_output(output).values()))
|
|
328
326
|
|
|
329
327
|
@staticmethod
|
|
330
328
|
def prune_and_parse_loudnorm_output(
|
|
331
|
-
output: str
|
|
332
|
-
) ->
|
|
329
|
+
output: str
|
|
330
|
+
) -> dict[int, EbuLoudnessStatistics]:
|
|
333
331
|
"""
|
|
334
332
|
Prune ffmpeg progress lines from output and parse the loudnorm filter output.
|
|
335
333
|
There may be multiple outputs if multiple streams were processed.
|
|
336
334
|
|
|
337
335
|
Args:
|
|
338
336
|
output (str): The output from ffmpeg.
|
|
339
|
-
num_stats (int): The number of loudnorm statistics to parse.
|
|
340
337
|
|
|
341
338
|
Returns:
|
|
342
339
|
list: The EBU loudness statistics.
|
|
343
340
|
"""
|
|
344
341
|
pruned_output = CommandRunner.prune_ffmpeg_progress_from_output(output)
|
|
345
342
|
output_lines = [line.strip() for line in pruned_output.split("\n")]
|
|
346
|
-
|
|
347
|
-
ret = []
|
|
348
|
-
idx = 0
|
|
349
|
-
while True:
|
|
350
|
-
_logger.debug(f"Parsing loudnorm stats for stream {idx}")
|
|
351
|
-
loudnorm_stats = AudioStream._parse_loudnorm_output(
|
|
352
|
-
output_lines, stream_index=idx
|
|
353
|
-
)
|
|
354
|
-
idx += 1
|
|
355
|
-
|
|
356
|
-
if loudnorm_stats is None:
|
|
357
|
-
continue
|
|
358
|
-
ret.append(loudnorm_stats)
|
|
359
|
-
|
|
360
|
-
if len(ret) >= num_stats:
|
|
361
|
-
break
|
|
362
|
-
|
|
363
|
-
return ret
|
|
343
|
+
return AudioStream._parse_loudnorm_output(output_lines)
|
|
364
344
|
|
|
365
345
|
@staticmethod
|
|
366
346
|
def _parse_loudnorm_output(
|
|
367
|
-
output_lines: list[str]
|
|
368
|
-
) ->
|
|
347
|
+
output_lines: list[str]
|
|
348
|
+
) -> dict[int, EbuLoudnessStatistics]:
|
|
369
349
|
"""
|
|
370
350
|
Parse the output of a loudnorm filter to get the EBU loudness statistics.
|
|
371
351
|
|
|
372
352
|
Args:
|
|
373
353
|
output_lines (list[str]): The output lines of the loudnorm filter.
|
|
374
|
-
stream_index (int): The stream index, optional to filter out the correct stream. If unset, the first stream is used.
|
|
375
354
|
|
|
376
355
|
Raises:
|
|
377
356
|
FFmpegNormalizeError: When the output could not be parsed.
|
|
@@ -379,64 +358,58 @@ class AudioStream(MediaStream):
|
|
|
379
358
|
Returns:
|
|
380
359
|
EbuLoudnessStatistics: The EBU loudness statistics, if found.
|
|
381
360
|
"""
|
|
361
|
+
result = dict[int, EbuLoudnessStatistics]()
|
|
362
|
+
stream_index = -1
|
|
382
363
|
loudnorm_start = 0
|
|
383
|
-
loudnorm_end = 0
|
|
384
364
|
for index, line in enumerate(output_lines):
|
|
385
|
-
if
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
loudnorm_stats[key] = float(loudnorm_stats[key])
|
|
434
|
-
|
|
435
|
-
return cast(EbuLoudnessStatistics, loudnorm_stats)
|
|
436
|
-
except Exception as e:
|
|
437
|
-
raise FFmpegNormalizeError(
|
|
438
|
-
f"Could not parse loudnorm stats; wrong JSON format in string: {e}"
|
|
439
|
-
)
|
|
365
|
+
if stream_index < 0:
|
|
366
|
+
if m := _loudnorm_pattern.match(line):
|
|
367
|
+
loudnorm_start = index + 1
|
|
368
|
+
stream_index = int(m.group(1))
|
|
369
|
+
else:
|
|
370
|
+
if line.startswith("}"):
|
|
371
|
+
loudnorm_end = index + 1
|
|
372
|
+
loudnorm_data = "\n".join(output_lines[loudnorm_start:loudnorm_end])
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
loudnorm_stats = json.loads(loudnorm_data)
|
|
376
|
+
|
|
377
|
+
_logger.debug(
|
|
378
|
+
f"Loudnorm stats for stream {stream_index} parsed: {loudnorm_data}"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
for key in [
|
|
382
|
+
"input_i",
|
|
383
|
+
"input_tp",
|
|
384
|
+
"input_lra",
|
|
385
|
+
"input_thresh",
|
|
386
|
+
"output_i",
|
|
387
|
+
"output_tp",
|
|
388
|
+
"output_lra",
|
|
389
|
+
"output_thresh",
|
|
390
|
+
"target_offset",
|
|
391
|
+
"normalization_type",
|
|
392
|
+
]:
|
|
393
|
+
if key not in loudnorm_stats:
|
|
394
|
+
continue
|
|
395
|
+
if key == "normalization_type":
|
|
396
|
+
loudnorm_stats[key] = loudnorm_stats[key].lower()
|
|
397
|
+
# handle infinite values
|
|
398
|
+
elif float(loudnorm_stats[key]) == -float("inf"):
|
|
399
|
+
loudnorm_stats[key] = -99
|
|
400
|
+
elif float(loudnorm_stats[key]) == float("inf"):
|
|
401
|
+
loudnorm_stats[key] = 0
|
|
402
|
+
else:
|
|
403
|
+
# convert to floats
|
|
404
|
+
loudnorm_stats[key] = float(loudnorm_stats[key])
|
|
405
|
+
|
|
406
|
+
result[stream_index] = cast(EbuLoudnessStatistics, loudnorm_stats)
|
|
407
|
+
stream_index = -1
|
|
408
|
+
except Exception as e:
|
|
409
|
+
raise FFmpegNormalizeError(
|
|
410
|
+
f"Could not parse loudnorm stats; wrong JSON format in string: {e}"
|
|
411
|
+
)
|
|
412
|
+
return result
|
|
440
413
|
|
|
441
414
|
def get_second_pass_opts_ebu(self) -> str:
|
|
442
415
|
"""
|
|
@@ -508,26 +481,40 @@ class AudioStream(MediaStream):
|
|
|
508
481
|
"Specify -ar/--sample-rate to override it."
|
|
509
482
|
)
|
|
510
483
|
|
|
484
|
+
target_level = self.ffmpeg_normalize.target_level
|
|
485
|
+
if self.ffmpeg_normalize.auto_lower_loudness_target:
|
|
486
|
+
safe_target = (
|
|
487
|
+
self.loudness_statistics["ebu_pass1"]["input_i"]
|
|
488
|
+
- self.loudness_statistics["ebu_pass1"]["input_tp"]
|
|
489
|
+
+ self.ffmpeg_normalize.true_peak
|
|
490
|
+
- 0.1
|
|
491
|
+
)
|
|
492
|
+
if safe_target < self.ffmpeg_normalize.target_level:
|
|
493
|
+
target_level = safe_target
|
|
494
|
+
_logger.warning(
|
|
495
|
+
f"Using loudness target {target_level} because --auto-lower-loudness-target given.",
|
|
496
|
+
)
|
|
497
|
+
|
|
511
498
|
stats = self.loudness_statistics["ebu_pass1"]
|
|
512
499
|
|
|
513
500
|
opts = {
|
|
514
|
-
"i":
|
|
501
|
+
"i": target_level,
|
|
515
502
|
"lra": self.media_file.ffmpeg_normalize.loudness_range_target,
|
|
516
503
|
"tp": self.media_file.ffmpeg_normalize.true_peak,
|
|
517
504
|
"offset": self._constrain(
|
|
518
|
-
|
|
505
|
+
stats["target_offset"], -99, 99, name="target_offset"
|
|
519
506
|
),
|
|
520
507
|
"measured_i": self._constrain(
|
|
521
|
-
|
|
508
|
+
stats["input_i"], -99, 0, name="input_i"
|
|
522
509
|
),
|
|
523
510
|
"measured_lra": self._constrain(
|
|
524
|
-
|
|
511
|
+
stats["input_lra"], 0, 99, name="input_lra"
|
|
525
512
|
),
|
|
526
513
|
"measured_tp": self._constrain(
|
|
527
|
-
|
|
514
|
+
stats["input_tp"], -99, 99, name="input_tp"
|
|
528
515
|
),
|
|
529
516
|
"measured_thresh": self._constrain(
|
|
530
|
-
|
|
517
|
+
stats["input_thresh"], -99, 0, name="input_thresh"
|
|
531
518
|
),
|
|
532
519
|
"linear": "false" if self.media_file.ffmpeg_normalize.dynamic else "true",
|
|
533
520
|
"print_format": "json",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.31.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ffmpeg-normalize
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.31.0
|
|
4
4
|
Summary: Normalize audio via ffmpeg
|
|
5
5
|
Home-page: https://github.com/slhck/ffmpeg-normalize
|
|
6
6
|
Author: Werner Robitza
|
|
@@ -15,12 +15,12 @@ Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
|
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Classifier: Natural Language :: English
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ License-File: LICENSE
|
|
|
31
31
|

|
|
32
32
|
|
|
33
33
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
34
|
-
[](#contributors-)
|
|
35
35
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
36
36
|
|
|
37
37
|
A utility for batch-normalizing audio using ffmpeg.
|
|
@@ -54,6 +54,7 @@ Read on for more info.
|
|
|
54
54
|
- [Requirements](#requirements)
|
|
55
55
|
- [ffmpeg](#ffmpeg)
|
|
56
56
|
- [Installation](#installation)
|
|
57
|
+
- [Shell Completions](#shell-completions)
|
|
57
58
|
- [Usage with Docker](#usage-with-docker)
|
|
58
59
|
- [High LeveL Introduction](#high-level-introduction)
|
|
59
60
|
- [Basic Usage](#basic-usage)
|
|
@@ -69,6 +70,7 @@ Read on for more info.
|
|
|
69
70
|
- [Environment Variables](#environment-variables)
|
|
70
71
|
- [API](#api)
|
|
71
72
|
- [FAQ](#faq)
|
|
73
|
+
- [My output file is too large?](#my-output-file-is-too-large)
|
|
72
74
|
- [What options should I choose for the EBU R128 filter? What is linear and dynamic mode?](#what-options-should-i-choose-for-the-ebu-r128-filter-what-is-linear-and-dynamic-mode)
|
|
73
75
|
- [The program doesn't work because the "loudnorm" filter can't be found](#the-program-doesnt-work-because-the-loudnorm-filter-cant-be-found)
|
|
74
76
|
- [Should I use this to normalize my music collection?](#should-i-use-this-to-normalize-my-music-collection)
|
|
@@ -88,7 +90,7 @@ Read on for more info.
|
|
|
88
90
|
|
|
89
91
|
## Requirements
|
|
90
92
|
|
|
91
|
-
You need Python 3.
|
|
93
|
+
You need Python 3.9 or higher, and ffmpeg.
|
|
92
94
|
|
|
93
95
|
### ffmpeg
|
|
94
96
|
|
|
@@ -129,6 +131,54 @@ Or download this repository, then run `pip3 install .`.
|
|
|
129
131
|
|
|
130
132
|
To later upgrade to the latest version, run `pip3 install --upgrade ffmpeg-normalize`.
|
|
131
133
|
|
|
134
|
+
### Shell Completions
|
|
135
|
+
|
|
136
|
+
This tool provides shell completions for bash and zsh. To install them:
|
|
137
|
+
|
|
138
|
+
<!--
|
|
139
|
+
Note to self: Generate the shtab ones with:
|
|
140
|
+
|
|
141
|
+
shtab --shell=bash -u ffmpeg_normalize.__main__.create_parser > completions/ffmpeg-normalize-shtab.bash
|
|
142
|
+
shtab --shell=zsh -u ffmpeg_normalize.__main__.create_parser > completions/ffmpeg-normalize-shtab.zsh
|
|
143
|
+
|
|
144
|
+
but these are not properly working yet.
|
|
145
|
+
-->
|
|
146
|
+
|
|
147
|
+
#### Bash
|
|
148
|
+
|
|
149
|
+
If you have [`bash-completion`](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to the `/usr/local/etc/bash_completion.d` directory.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize-completion.bash \
|
|
153
|
+
-o /usr/local/etc/bash_completion.d/ffmpeg-normalize
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Without bash-completion, you can manually install the completion script:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# create completions directory if it doesn't exist
|
|
160
|
+
mkdir -p ~/.bash_completions.d
|
|
161
|
+
|
|
162
|
+
# download and install completion script
|
|
163
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize-completion.bash \
|
|
164
|
+
-o ~/.bash_completions.d/ffmpeg-normalize
|
|
165
|
+
|
|
166
|
+
# source it in your ~/.bashrc
|
|
167
|
+
echo 'source ~/.bash_completions.d/ffmpeg-normalize' >> ~/.bashrc
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Zsh
|
|
171
|
+
|
|
172
|
+
Download the completion script and place it in the default `site-functions` directory:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
curl -L https://raw.githubusercontent.com/slhck/ffmpeg-normalize/master/completions/ffmpeg-normalize.zsh \
|
|
176
|
+
-o /usr/local/share/zsh/site-functions/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
You may choose any other directory that is in your `$FPATH` variable.
|
|
180
|
+
Make sure your `.zshrc` file contains `autoload -Uz compinit && compinit`.
|
|
181
|
+
|
|
132
182
|
## Usage with Docker
|
|
133
183
|
|
|
134
184
|
You can use the pre-built image from Docker Hub:
|
|
@@ -280,10 +330,7 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
280
330
|
|
|
281
331
|
- `--keep-lra-above-loudness-range-target`: Keep input loudness range above loudness range target.
|
|
282
332
|
|
|
283
|
-
|
|
284
|
-
- keep input loudness range target above `LOUDNESS_RANGE_TARGET`.
|
|
285
|
-
|
|
286
|
-
as alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
333
|
+
Can be used as an alternative to `--keep-loudness-range-target` to allow for linear normalization.
|
|
287
334
|
|
|
288
335
|
- `-tp TRUE_PEAK, --true-peak TRUE_PEAK`: EBU Maximum True Peak in dBTP (default: -2.0).
|
|
289
336
|
|
|
@@ -295,6 +342,16 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
295
342
|
|
|
296
343
|
Range is -99.0 - +99.0.
|
|
297
344
|
|
|
345
|
+
- `--lower-only`: Whether the audio should not increase in loudness.
|
|
346
|
+
|
|
347
|
+
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.
|
|
348
|
+
|
|
349
|
+
- `--auto-lower-loudness-target`: Automatically lower EBU Integrated Loudness Target.
|
|
350
|
+
|
|
351
|
+
Automatically lower EBU Integrated Loudness Target to prevent falling back to dynamic filtering.
|
|
352
|
+
|
|
353
|
+
Makes sure target loudness is lower than measured loudness minus peak loudness (input_i - input_tp) by a small amount.
|
|
354
|
+
|
|
298
355
|
- `--dual-mono`: Treat mono input files as "dual-mono".
|
|
299
356
|
|
|
300
357
|
If a mono file is intended for playback on a stereo system, its EBU R128 measurement will be perceptually incorrect. If set, this option will compensate for this effect. Multi-channel input files are not affected by this option.
|
|
@@ -303,7 +360,7 @@ For more information on the options (`[options]`) available, run `ffmpeg-normali
|
|
|
303
360
|
|
|
304
361
|
Instead of applying linear EBU R128 normalization, choose a dynamic normalization. This is not usually recommended.
|
|
305
362
|
|
|
306
|
-
Dynamic mode will automatically change the sample rate to 192 kHz. Use
|
|
363
|
+
Dynamic mode will automatically change the sample rate to 192 kHz. Use `-ar`/`--sample-rate` to specify a different output sample rate.
|
|
307
364
|
|
|
308
365
|
### Audio Encoding
|
|
309
366
|
|
|
@@ -405,6 +462,16 @@ For more information see the [API documentation](https://htmlpreview.github.io/?
|
|
|
405
462
|
|
|
406
463
|
## FAQ
|
|
407
464
|
|
|
465
|
+
### My output file is too large?
|
|
466
|
+
|
|
467
|
+
This is because the default output codec is PCM, which is uncompressed. If you want to reduce the file size, you can specify an audio codec with `-c:a` (e.g., `-c:a aac` for ffmpeg's built-in AAC encoder), and optionally a bitrate with `-b:a`.
|
|
468
|
+
|
|
469
|
+
For example:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
ffmpeg-normalize input.wav -o output.m4a -c:a aac -b:a 192k
|
|
473
|
+
```
|
|
474
|
+
|
|
408
475
|
### What options should I choose for the EBU R128 filter? What is linear and dynamic mode?
|
|
409
476
|
|
|
410
477
|
EBU R128 is a method for normalizing audio loudness across different tracks or programs. It works by analyzing the audio content and adjusting it to meet specific loudness targets. The main components are:
|
|
@@ -429,7 +496,11 @@ For most cases, linear mode is recommended. Dynamic mode should only be used whe
|
|
|
429
496
|
|
|
430
497
|
* When the required gain adjustment to meet the integrated loudness target would result in the true peak exceeding the specified true peak limit. This is because linear processing alone cannot reduce peaks without affecting the entire signal. For example, if a file needs to be amplified by 6 dB to reach the target integrated loudness, but doing so would push the true peak above the specified limit, the filter might switch to dynamic mode to handle this situation. If your content allows for it, you can increase the true peak target to give more headroom for linear processing. If you're consistently running into true peak issues, you might also consider lowering your target integrated loudness level.
|
|
431
498
|
|
|
432
|
-
At this time, the `loudnorm` filter in ffmpeg does not provide a way to force linear mode when the input loudness range exceeds the target or when the true peak would be exceeded.
|
|
499
|
+
At this time, the `loudnorm` filter in ffmpeg does not provide a way to force linear mode when the input loudness range exceeds the target or when the true peak would be exceeded. There are some options to mitigate this:
|
|
500
|
+
|
|
501
|
+
- The `--keep-lra-above-loudness-range-target` option can be used to keep the input loudness range above the specified target, but it will not force linear mode in all cases.
|
|
502
|
+
- Similarly, the `--keep-loudness-range-target` option can be used to keep the input loudness range target.
|
|
503
|
+
- The `--lower-only` option can be used to skip the normalization pass completely if the measured loudness is lower than the target loudness.
|
|
433
504
|
|
|
434
505
|
### The program doesn't work because the "loudnorm" filter can't be found
|
|
435
506
|
|
|
@@ -548,6 +619,7 @@ If you found this program useful and feel like giving back, feel free to send a
|
|
|
548
619
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/psavva"><img src="https://avatars.githubusercontent.com/u/1454758?v=4?s=100" width="100px;" alt="Panayiotis Savva"/><br /><sub><b>Panayiotis Savva</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=psavva" title="Code">💻</a></td>
|
|
549
620
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HighMans"><img src="https://avatars.githubusercontent.com/u/42877729?v=4?s=100" width="100px;" alt="HighMans"/><br /><sub><b>HighMans</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=HighMans" title="Code">💻</a></td>
|
|
550
621
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kanjieater"><img src="https://avatars.githubusercontent.com/u/32607317?v=4?s=100" width="100px;" alt="kanjieater"/><br /><sub><b>kanjieater</b></sub></a><br /><a href="#ideas-kanjieater" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
622
|
+
<td align="center" valign="top" width="14.28%"><a href="https://ahmetsait.com/"><img src="https://avatars.githubusercontent.com/u/8372246?v=4?s=100" width="100px;" alt="Ahmet Sait"/><br /><sub><b>Ahmet Sait</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=ahmetsait" title="Code">💻</a></td>
|
|
551
623
|
</tr>
|
|
552
624
|
</tbody>
|
|
553
625
|
<tfoot>
|
|
@@ -595,6 +667,44 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
595
667
|
# Changelog
|
|
596
668
|
|
|
597
669
|
|
|
670
|
+
## v1.31.0 (2024-12-15)
|
|
671
|
+
|
|
672
|
+
* Update docs and completions.
|
|
673
|
+
|
|
674
|
+
* Implement `--auto-lower-loudness-target`
|
|
675
|
+
|
|
676
|
+
* Fix deprecations and mypy --strict errors.
|
|
677
|
+
|
|
678
|
+
* Feat: add completions.
|
|
679
|
+
|
|
680
|
+
* Docs: update explainer.
|
|
681
|
+
|
|
682
|
+
* Docs: update docs to include lower-only.
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
## v1.30.0 (2024-11-22)
|
|
686
|
+
|
|
687
|
+
* Change lower-only message to warning.
|
|
688
|
+
|
|
689
|
+
* Make setup name PEP 625 compliant.
|
|
690
|
+
|
|
691
|
+
* Docs: add @ahmetsait as a contributor.
|
|
692
|
+
|
|
693
|
+
* Implement `--lower-only`
|
|
694
|
+
|
|
695
|
+
* Fix: `--print-stats` only outputs the last stream.
|
|
696
|
+
|
|
697
|
+
* More robust `loudnorm` output parsing.
|
|
698
|
+
|
|
699
|
+
* Remove unnecessary conversions.
|
|
700
|
+
|
|
701
|
+
* Update .editorconfig.
|
|
702
|
+
|
|
703
|
+
* Remove python 3.8, add python 3.12, 3.13.
|
|
704
|
+
|
|
705
|
+
* Add README on file size.
|
|
706
|
+
|
|
707
|
+
|
|
598
708
|
## v1.29.2 (2024-11-18)
|
|
599
709
|
|
|
600
710
|
* Fix: show percentage with two decimal digits in progress.
|
|
@@ -19,7 +19,7 @@ with open(path.join(here, "CHANGELOG.md"), encoding="utf8") as f:
|
|
|
19
19
|
history = f.read()
|
|
20
20
|
|
|
21
21
|
setup(
|
|
22
|
-
name="
|
|
22
|
+
name="ffmpeg_normalize",
|
|
23
23
|
version=version,
|
|
24
24
|
description="Normalize audio via ffmpeg",
|
|
25
25
|
long_description=long_description + "\n\n" + history,
|
|
@@ -50,13 +50,13 @@ setup(
|
|
|
50
50
|
"License :: OSI Approved :: MIT License",
|
|
51
51
|
"Natural Language :: English",
|
|
52
52
|
"Programming Language :: Python :: 3",
|
|
53
|
-
"Programming Language :: Python :: 3.8",
|
|
54
53
|
"Programming Language :: Python :: 3.9",
|
|
55
54
|
"Programming Language :: Python :: 3.10",
|
|
56
55
|
"Programming Language :: Python :: 3.11",
|
|
57
56
|
"Programming Language :: Python :: 3.12",
|
|
57
|
+
"Programming Language :: Python :: 3.13",
|
|
58
58
|
],
|
|
59
|
-
python_requires=">=3.
|
|
59
|
+
python_requires=">=3.9",
|
|
60
60
|
entry_points={
|
|
61
61
|
"console_scripts": ["ffmpeg-normalize = ffmpeg_normalize.__main__:main"]
|
|
62
62
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.29.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{ffmpeg-normalize-1.29.2 → ffmpeg_normalize-1.31.0}/ffmpeg_normalize.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|