ffmpeg-normalize 1.32.1__tar.gz → 1.33.1__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.33.1/LICENSE.md +9 -0
- ffmpeg_normalize-1.33.1/PKG-INFO +120 -0
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1}/README.md +8 -5
- ffmpeg_normalize-1.33.1/pyproject.toml +67 -0
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/__init__.py +4 -1
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/__main__.py +23 -3
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/_cmd_utils.py +3 -3
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/_ffmpeg_normalize.py +1 -1
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/_media_file.py +104 -38
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/_streams.py +32 -3
- ffmpeg_normalize-1.32.1/CHANGELOG.md +0 -1303
- ffmpeg_normalize-1.32.1/LICENSE +0 -21
- ffmpeg_normalize-1.32.1/PKG-INFO +0 -1416
- ffmpeg_normalize-1.32.1/ffmpeg_normalize/_version.py +0 -1
- ffmpeg_normalize-1.32.1/ffmpeg_normalize.egg-info/PKG-INFO +0 -1416
- ffmpeg_normalize-1.32.1/ffmpeg_normalize.egg-info/SOURCES.txt +0 -26
- ffmpeg_normalize-1.32.1/ffmpeg_normalize.egg-info/dependency_links.txt +0 -1
- ffmpeg_normalize-1.32.1/ffmpeg_normalize.egg-info/entry_points.txt +0 -2
- ffmpeg_normalize-1.32.1/ffmpeg_normalize.egg-info/not-zip-safe +0 -1
- ffmpeg_normalize-1.32.1/ffmpeg_normalize.egg-info/requires.txt +0 -7
- ffmpeg_normalize-1.32.1/ffmpeg_normalize.egg-info/top_level.txt +0 -1
- ffmpeg_normalize-1.32.1/setup.cfg +0 -18
- ffmpeg_normalize-1.32.1/setup.py +0 -64
- ffmpeg_normalize-1.32.1/test/out.mp4 +0 -0
- ffmpeg_normalize-1.32.1/test/test.mp4 +0 -0
- ffmpeg_normalize-1.32.1/test/test.py +0 -489
- ffmpeg_normalize-1.32.1/test/test.wav +0 -0
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/_errors.py +0 -0
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/_logger.py +0 -0
- {ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/py.typed +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
ffmpeg-normalize, Copyright (c) Werner Robitza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ffmpeg-normalize
|
|
3
|
+
Version: 1.33.1
|
|
4
|
+
Summary: Normalize audio via ffmpeg
|
|
5
|
+
Keywords: ffmpeg,normalize,audio
|
|
6
|
+
Author: Werner Robitza
|
|
7
|
+
Author-email: Werner Robitza <werner.robitza@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
13
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
|
14
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Requires-Dist: tqdm>=4.64.1
|
|
24
|
+
Requires-Dist: colorama>=0.4.6 ; sys_platform == 'win32'
|
|
25
|
+
Requires-Dist: ffmpeg-progress-yield>=1.0.1
|
|
26
|
+
Requires-Dist: colorlog==6.7.0
|
|
27
|
+
Requires-Dist: mutagen>=1.47.0
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Project-URL: Homepage, https://github.com/slhck/ffmpeg-normalize
|
|
30
|
+
Project-URL: Repository, https://github.com/slhck/ffmpeg-normalize
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# ffmpeg-normalize
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/ffmpeg-normalize)
|
|
36
|
+

|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
40
|
+
[](#contributors-)
|
|
41
|
+
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
42
|
+
|
|
43
|
+
A utility for batch-normalizing audio using ffmpeg.
|
|
44
|
+
|
|
45
|
+
This program normalizes media files to a certain loudness level using the EBU R128 loudness normalization procedure. It can also perform RMS-based normalization (where the mean is lifted or attenuated), or peak normalization to a certain target level.
|
|
46
|
+
|
|
47
|
+
## ✨ Features
|
|
48
|
+
|
|
49
|
+
- EBU R128 loudness normalization (two-pass by default, with an option for one-pass dynamic normalization)
|
|
50
|
+
- RMS-based normalization
|
|
51
|
+
- Peak normalization
|
|
52
|
+
- Video file support
|
|
53
|
+
- Docker support
|
|
54
|
+
- Python API
|
|
55
|
+
|
|
56
|
+
## 🚀 Quick Start
|
|
57
|
+
|
|
58
|
+
1. Install a recent version of [ffmpeg](https://ffmpeg.org/download.html)
|
|
59
|
+
2. Run `pip3 install ffmpeg-normalize` and `ffmpeg-normalize /path/to/your/file.mp4`, alternatively install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) and run `uvx ffmpeg-normalize /path/to/your/file.mp4`
|
|
60
|
+
3. Done! 🎧 (the normalized file will be called `normalized/file.mkv`)
|
|
61
|
+
|
|
62
|
+
## 📓 Documentation
|
|
63
|
+
|
|
64
|
+
Check out our [documentation](https://slhck.info/ffmpeg-normalize/) for more info!
|
|
65
|
+
|
|
66
|
+
## 🤝 Contributors
|
|
67
|
+
|
|
68
|
+
The only reason this project exists in its current form is because [@benjaoming](https://github.com/slhck/ffmpeg-normalize/issues?q=is%3Apr+author%3Abenjaoming)'s initial PRs. Thanks for everyone's support!
|
|
69
|
+
|
|
70
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
71
|
+
<!-- prettier-ignore-start -->
|
|
72
|
+
<!-- markdownlint-disable -->
|
|
73
|
+
<table>
|
|
74
|
+
<tbody>
|
|
75
|
+
<tr>
|
|
76
|
+
<td align="center" valign="top" width="14.28%"><a href="https://overtag.dk/"><img src="https://avatars.githubusercontent.com/u/374612?v=4?s=100" width="100px;" alt="Benjamin Balder Bach"/><br /><sub><b>Benjamin Balder Bach</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=benjaoming" title="Code">💻</a></td>
|
|
77
|
+
<td align="center" valign="top" width="14.28%"><a href="https://chaos.social/@eleni"><img src="https://avatars.githubusercontent.com/u/511547?v=4?s=100" width="100px;" alt="Eleni Lixourioti"/><br /><sub><b>Eleni Lixourioti</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=Geekfish" title="Code">💻</a></td>
|
|
78
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/thenewguy"><img src="https://avatars.githubusercontent.com/u/77731?v=4?s=100" width="100px;" alt="thenewguy"/><br /><sub><b>thenewguy</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=thenewguy" title="Code">💻</a></td>
|
|
79
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aviolo"><img src="https://avatars.githubusercontent.com/u/560229?v=4?s=100" width="100px;" alt="Anthony Violo"/><br /><sub><b>Anthony Violo</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=aviolo" title="Code">💻</a></td>
|
|
80
|
+
<td align="center" valign="top" width="14.28%"><a href="https://jacobs.af/"><img src="https://avatars.githubusercontent.com/u/952830?v=4?s=100" width="100px;" alt="Eric Jacobs"/><br /><sub><b>Eric Jacobs</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=jetpks" title="Code">💻</a></td>
|
|
81
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kostalski"><img src="https://avatars.githubusercontent.com/u/34033008?v=4?s=100" width="100px;" alt="kostalski"/><br /><sub><b>kostalski</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=kostalski" title="Code">💻</a></td>
|
|
82
|
+
<td align="center" valign="top" width="14.28%"><a href="http://justinppearson.com/"><img src="https://avatars.githubusercontent.com/u/8844823?v=4?s=100" width="100px;" alt="Justin Pearson"/><br /><sub><b>Justin Pearson</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=justinpearson" title="Code">💻</a></td>
|
|
83
|
+
</tr>
|
|
84
|
+
<tr>
|
|
85
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nottt"><img src="https://avatars.githubusercontent.com/u/13532436?v=4?s=100" width="100px;" alt="ad90xa0-aa"/><br /><sub><b>ad90xa0-aa</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=Nottt" title="Code">💻</a></td>
|
|
86
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mathijsz"><img src="https://avatars.githubusercontent.com/u/1891187?v=4?s=100" width="100px;" alt="Mathijs"/><br /><sub><b>Mathijs</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=Mathijsz" title="Code">💻</a></td>
|
|
87
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mpuels"><img src="https://avatars.githubusercontent.com/u/2924816?v=4?s=100" width="100px;" alt="Marc Püls"/><br /><sub><b>Marc Püls</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=mpuels" title="Code">💻</a></td>
|
|
88
|
+
<td align="center" valign="top" width="14.28%"><a href="http://www.mvbattista.com/"><img src="https://avatars.githubusercontent.com/u/158287?v=4?s=100" width="100px;" alt="Michael V. Battista"/><br /><sub><b>Michael V. Battista</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=mvbattista" title="Code">💻</a></td>
|
|
89
|
+
<td align="center" valign="top" width="14.28%"><a href="http://auto-editor.com"><img src="https://avatars.githubusercontent.com/u/57511737?v=4?s=100" width="100px;" alt="WyattBlue"/><br /><sub><b>WyattBlue</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=WyattBlue" title="Code">💻</a></td>
|
|
90
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/g3n35i5"><img src="https://avatars.githubusercontent.com/u/17593457?v=4?s=100" width="100px;" alt="Jan-Frederik Schmidt"/><br /><sub><b>Jan-Frederik Schmidt</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=g3n35i5" title="Code">💻</a></td>
|
|
91
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mjhalwa"><img src="https://avatars.githubusercontent.com/u/8994014?v=4?s=100" width="100px;" alt="mjhalwa"/><br /><sub><b>mjhalwa</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=mjhalwa" title="Code">💻</a></td>
|
|
92
|
+
</tr>
|
|
93
|
+
<tr>
|
|
94
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/07416"><img src="https://avatars.githubusercontent.com/u/14923168?v=4?s=100" width="100px;" alt="07416"/><br /><sub><b>07416</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=07416" title="Documentation">📖</a></td>
|
|
95
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sian1468"><img src="https://avatars.githubusercontent.com/u/58017832?v=4?s=100" width="100px;" alt="sian1468"/><br /><sub><b>sian1468</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=sian1468" title="Tests">⚠️</a></td>
|
|
96
|
+
<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>
|
|
97
|
+
<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>
|
|
98
|
+
<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>
|
|
99
|
+
<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>
|
|
100
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/georgev93"><img src="https://avatars.githubusercontent.com/u/39860568?v=4?s=100" width="100px;" alt="georgev93"/><br /><sub><b>georgev93</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=georgev93" title="Code">💻</a></td>
|
|
101
|
+
</tr>
|
|
102
|
+
<tr>
|
|
103
|
+
<td align="center" valign="top" width="14.28%"><a href="https://davidbern.com/"><img src="https://avatars.githubusercontent.com/u/371066?v=4?s=100" width="100px;" alt="David Bern"/><br /><sub><b>David Bern</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=odie5533" title="Code">💻</a></td>
|
|
104
|
+
</tr>
|
|
105
|
+
</tbody>
|
|
106
|
+
<tfoot>
|
|
107
|
+
<tr>
|
|
108
|
+
<td align="center" size="13px" colspan="7">
|
|
109
|
+
<img src="https://raw.githubusercontent.com/all-contributors/all-contributors-cli/1b8533af435da9854653492b1327a23a4dbd0a10/assets/logo-small.svg">
|
|
110
|
+
<a href="https://all-contributors.js.org/docs/en/bot/usage">Add your contributions</a>
|
|
111
|
+
</img>
|
|
112
|
+
</td>
|
|
113
|
+
</tr>
|
|
114
|
+
</tfoot>
|
|
115
|
+
</table>
|
|
116
|
+
|
|
117
|
+
<!-- markdownlint-restore -->
|
|
118
|
+
<!-- prettier-ignore-end -->
|
|
119
|
+
|
|
120
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
@@ -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.
|
|
@@ -14,7 +14,7 @@ This program normalizes media files to a certain loudness level using the EBU R1
|
|
|
14
14
|
|
|
15
15
|
## ✨ Features
|
|
16
16
|
|
|
17
|
-
- EBU R128 loudness normalization
|
|
17
|
+
- EBU R128 loudness normalization (two-pass by default, with an option for one-pass dynamic normalization)
|
|
18
18
|
- RMS-based normalization
|
|
19
19
|
- Peak normalization
|
|
20
20
|
- Video file support
|
|
@@ -24,9 +24,8 @@ This program normalizes media files to a certain loudness level using the EBU R1
|
|
|
24
24
|
## 🚀 Quick Start
|
|
25
25
|
|
|
26
26
|
1. Install a recent version of [ffmpeg](https://ffmpeg.org/download.html)
|
|
27
|
-
2. Run `pip3 install ffmpeg-normalize`
|
|
28
|
-
3.
|
|
29
|
-
4. Done! 🎧 (the normalized file will be called `normalized/file.mkv`)
|
|
27
|
+
2. Run `pip3 install ffmpeg-normalize` and `ffmpeg-normalize /path/to/your/file.mp4`, alternatively install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) and run `uvx ffmpeg-normalize /path/to/your/file.mp4`
|
|
28
|
+
3. Done! 🎧 (the normalized file will be called `normalized/file.mkv`)
|
|
30
29
|
|
|
31
30
|
## 📓 Documentation
|
|
32
31
|
|
|
@@ -66,6 +65,10 @@ The only reason this project exists in its current form is because [@benjaoming]
|
|
|
66
65
|
<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>
|
|
67
66
|
<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>
|
|
68
67
|
<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>
|
|
68
|
+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/georgev93"><img src="https://avatars.githubusercontent.com/u/39860568?v=4?s=100" width="100px;" alt="georgev93"/><br /><sub><b>georgev93</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=georgev93" title="Code">💻</a></td>
|
|
69
|
+
</tr>
|
|
70
|
+
<tr>
|
|
71
|
+
<td align="center" valign="top" width="14.28%"><a href="https://davidbern.com/"><img src="https://avatars.githubusercontent.com/u/371066?v=4?s=100" width="100px;" alt="David Bern"/><br /><sub><b>David Bern</b></sub></a><br /><a href="https://github.com/slhck/ffmpeg-normalize/commits?author=odie5533" title="Code">💻</a></td>
|
|
69
72
|
</tr>
|
|
70
73
|
</tbody>
|
|
71
74
|
<tfoot>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.8.14,<0.9.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ffmpeg-normalize"
|
|
7
|
+
version = "1.33.1"
|
|
8
|
+
description = "Normalize audio via ffmpeg"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE.md"]
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Werner Robitza", email = "werner.robitza@gmail.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ffmpeg", "normalize", "audio"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
20
|
+
"Topic :: Multimedia :: Sound/Audio :: Analysis",
|
|
21
|
+
"Topic :: Multimedia :: Sound/Audio :: Conversion",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Natural Language :: English",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
]
|
|
31
|
+
requires-python = ">=3.9"
|
|
32
|
+
dependencies = [
|
|
33
|
+
"tqdm>=4.64.1",
|
|
34
|
+
"colorama>=0.4.6; platform_system=='Windows'",
|
|
35
|
+
"ffmpeg-progress-yield>=1.0.1",
|
|
36
|
+
"colorlog==6.7.0",
|
|
37
|
+
"mutagen>=1.47.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/slhck/ffmpeg-normalize"
|
|
42
|
+
Repository = "https://github.com/slhck/ffmpeg-normalize"
|
|
43
|
+
|
|
44
|
+
[project.scripts]
|
|
45
|
+
ffmpeg-normalize = "ffmpeg_normalize.__main__:main"
|
|
46
|
+
|
|
47
|
+
[tool.uv_build]
|
|
48
|
+
src-layout = true
|
|
49
|
+
|
|
50
|
+
[dependency-groups]
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=8.1.1,<9",
|
|
53
|
+
"ruff>=0.12.11",
|
|
54
|
+
"mypy>=1.0.0",
|
|
55
|
+
"types-tqdm",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
python_version = "3.13"
|
|
60
|
+
ignore_missing_imports = true
|
|
61
|
+
exclude = ["build"]
|
|
62
|
+
namespace_packages = false
|
|
63
|
+
no_implicit_optional = true
|
|
64
|
+
check_untyped_defs = true
|
|
65
|
+
warn_return_any = true
|
|
66
|
+
warn_unused_ignores = true
|
|
67
|
+
show_error_codes = true
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
|
|
1
3
|
from ._errors import FFmpegNormalizeError
|
|
2
4
|
from ._ffmpeg_normalize import FFmpegNormalize
|
|
3
5
|
from ._media_file import MediaFile
|
|
4
6
|
from ._streams import AudioStream, MediaStream, SubtitleStream, VideoStream
|
|
5
|
-
|
|
7
|
+
|
|
8
|
+
__version__ = importlib.metadata.version("ffmpeg-normalize")
|
|
6
9
|
|
|
7
10
|
__module_name__ = "ffmpeg_normalize"
|
|
8
11
|
|
|
@@ -13,7 +13,11 @@ from typing import NoReturn
|
|
|
13
13
|
from ._errors import FFmpegNormalizeError
|
|
14
14
|
from ._ffmpeg_normalize import NORMALIZATION_TYPES, FFmpegNormalize
|
|
15
15
|
from ._logger import setup_cli_logger
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
# Import version from package
|
|
18
|
+
import importlib.metadata
|
|
19
|
+
|
|
20
|
+
__version__ = importlib.metadata.version("ffmpeg-normalize")
|
|
17
21
|
|
|
18
22
|
_logger = logging.getLogger(__name__)
|
|
19
23
|
|
|
@@ -52,7 +56,12 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
52
56
|
)
|
|
53
57
|
|
|
54
58
|
group_io = parser.add_argument_group("File Input/output")
|
|
55
|
-
group_io.add_argument("input", nargs="
|
|
59
|
+
group_io.add_argument("input", nargs="*", help="Input media file(s)")
|
|
60
|
+
group_io.add_argument(
|
|
61
|
+
"--input-list",
|
|
62
|
+
type=str,
|
|
63
|
+
help="Path to a text file containing a line-separated list of input files",
|
|
64
|
+
)
|
|
56
65
|
group_io.add_argument(
|
|
57
66
|
"-o",
|
|
58
67
|
"--output",
|
|
@@ -583,7 +592,18 @@ def main() -> None:
|
|
|
583
592
|
"Will apply default file naming for the remaining ones."
|
|
584
593
|
)
|
|
585
594
|
|
|
586
|
-
|
|
595
|
+
# Collect input files from positional args and --input-list
|
|
596
|
+
input_files = list(cli_args.input) if cli_args.input else []
|
|
597
|
+
if cli_args.input_list:
|
|
598
|
+
if not os.path.exists(cli_args.input_list):
|
|
599
|
+
error(f"Input list file '{cli_args.input_list}' does not exist")
|
|
600
|
+
with open(cli_args.input_list, "r") as f:
|
|
601
|
+
input_files.extend([line.strip() for line in f if line.strip()])
|
|
602
|
+
|
|
603
|
+
if not input_files:
|
|
604
|
+
error("No input files specified. Use positional arguments or --input-list.")
|
|
605
|
+
|
|
606
|
+
for index, input_file in enumerate(input_files):
|
|
587
607
|
if cli_args.output is not None and index < len(cli_args.output):
|
|
588
608
|
if cli_args.output_folder and cli_args.output_folder != "normalized":
|
|
589
609
|
_logger.warning(
|
|
@@ -75,10 +75,10 @@ class CommandRunner:
|
|
|
75
75
|
"""
|
|
76
76
|
# wrapper for 'ffmpeg-progress-yield'
|
|
77
77
|
_logger.debug(f"Running command: {shlex.join(cmd)}")
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
with FfmpegProgress(cmd, dry_run=self.dry) as ff:
|
|
79
|
+
yield from ff.run_command_with_progress()
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
self.output = ff.stderr
|
|
82
82
|
|
|
83
83
|
if _logger.getEffectiveLevel() == logging.DEBUG and self.output is not None:
|
|
84
84
|
_logger.debug(
|
{ffmpeg_normalize-1.32.1 → ffmpeg_normalize-1.33.1/src}/ffmpeg_normalize/_ffmpeg_normalize.py
RENAMED
|
@@ -63,7 +63,7 @@ class FFmpegNormalize:
|
|
|
63
63
|
lower_only (bool, optional): Whether the audio should not increase in loudness. Defaults to False.
|
|
64
64
|
auto_lower_loudness_target (bool, optional): Automatically lower EBU Integrated Loudness Target.
|
|
65
65
|
dual_mono (bool, optional): Dual mono. Defaults to False.
|
|
66
|
-
dynamic (bool, optional):
|
|
66
|
+
dynamic (bool, optional): Use dynamic EBU R128 normalization. This is a one-pass algorithm and skips the initial media scan. Defaults to False.
|
|
67
67
|
audio_codec (str, optional): Audio codec. Defaults to "pcm_s16le".
|
|
68
68
|
audio_bitrate (float, optional): Audio bitrate. Defaults to None.
|
|
69
69
|
sample_rate (int, optional): Sample rate. Defaults to None.
|
|
@@ -6,7 +6,7 @@ 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, Iterable, Iterator, Literal, TypedDict
|
|
9
|
+
from typing import TYPE_CHECKING, Iterable, Iterator, Literal, TypedDict, Union
|
|
10
10
|
|
|
11
11
|
from mutagen.id3 import ID3, TXXX
|
|
12
12
|
from mutagen.mp3 import MP3
|
|
@@ -75,10 +75,17 @@ class MediaFile:
|
|
|
75
75
|
current_ext = os.path.splitext(output_file)[1][1:]
|
|
76
76
|
# we need to check if it's empty, e.g. /dev/null or NUL
|
|
77
77
|
if current_ext == "" or self.output_file == os.devnull:
|
|
78
|
+
_logger.debug(
|
|
79
|
+
f"Current extension is unset, or output file is a null device, using extension: {self.ffmpeg_normalize.extension}"
|
|
80
|
+
)
|
|
78
81
|
self.output_ext = self.ffmpeg_normalize.extension
|
|
79
82
|
else:
|
|
83
|
+
_logger.debug(
|
|
84
|
+
f"Current extension is set from output file, using extension: {current_ext}"
|
|
85
|
+
)
|
|
80
86
|
self.output_ext = current_ext
|
|
81
87
|
self.streams: StreamDict = {"audio": {}, "video": {}, "subtitle": {}}
|
|
88
|
+
self.temp_file: Union[str, None] = None
|
|
82
89
|
|
|
83
90
|
self.parse_streams()
|
|
84
91
|
|
|
@@ -200,15 +207,28 @@ class MediaFile:
|
|
|
200
207
|
"""
|
|
201
208
|
_logger.debug(f"Running normalization for {self.input_file}")
|
|
202
209
|
|
|
203
|
-
# run the first pass to get loudness stats
|
|
204
|
-
|
|
210
|
+
# run the first pass to get loudness stats, unless in dynamic EBU mode
|
|
211
|
+
if not (
|
|
212
|
+
self.ffmpeg_normalize.dynamic
|
|
213
|
+
and self.ffmpeg_normalize.normalization_type == "ebu"
|
|
214
|
+
):
|
|
215
|
+
self._first_pass()
|
|
216
|
+
else:
|
|
217
|
+
_logger.debug(
|
|
218
|
+
"Dynamic EBU mode: First pass will not run, as it is not needed."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# for second pass, create a temp file
|
|
222
|
+
temp_dir = mkdtemp()
|
|
223
|
+
self.temp_file = os.path.join(temp_dir, f"out.{self.output_ext}")
|
|
205
224
|
|
|
206
|
-
# shortcut to apply replaygain
|
|
207
225
|
if self.ffmpeg_normalize.replaygain:
|
|
208
|
-
|
|
209
|
-
|
|
226
|
+
_logger.debug(
|
|
227
|
+
"ReplayGain mode: Second pass will run with temporary file to get stats."
|
|
228
|
+
)
|
|
229
|
+
self.output_file = self.temp_file
|
|
210
230
|
|
|
211
|
-
# run the second pass as a whole
|
|
231
|
+
# run the second pass as a whole.
|
|
212
232
|
if self.ffmpeg_normalize.progress:
|
|
213
233
|
with tqdm(
|
|
214
234
|
total=100,
|
|
@@ -222,7 +242,20 @@ class MediaFile:
|
|
|
222
242
|
for _ in self._second_pass():
|
|
223
243
|
pass
|
|
224
244
|
|
|
225
|
-
|
|
245
|
+
# remove temp dir; this will remove the temp file as well if it has not been renamed (e.g. for replaygain)
|
|
246
|
+
if os.path.exists(temp_dir):
|
|
247
|
+
rmtree(temp_dir, ignore_errors=True)
|
|
248
|
+
|
|
249
|
+
# This will use stats from ebu_pass2 if available (from the main second pass),
|
|
250
|
+
# or fall back to ebu_pass1.
|
|
251
|
+
if self.ffmpeg_normalize.replaygain:
|
|
252
|
+
_logger.debug(
|
|
253
|
+
"ReplayGain tagging is enabled. Proceeding with tag calculation/application."
|
|
254
|
+
)
|
|
255
|
+
self._run_replaygain()
|
|
256
|
+
|
|
257
|
+
if not self.ffmpeg_normalize.replaygain:
|
|
258
|
+
_logger.info(f"Normalized file written to {self.output_file}")
|
|
226
259
|
|
|
227
260
|
def _run_replaygain(self) -> None:
|
|
228
261
|
"""
|
|
@@ -233,13 +266,32 @@ class MediaFile:
|
|
|
233
266
|
# get the audio streams
|
|
234
267
|
audio_streams = list(self.streams["audio"].values())
|
|
235
268
|
|
|
236
|
-
#
|
|
237
|
-
|
|
269
|
+
# Attempt to use EBU pass 2 statistics, which account for pre-filters.
|
|
270
|
+
# These are populated by the main second pass if it runs (not a dry run)
|
|
271
|
+
# and normalization_type is 'ebu'.
|
|
272
|
+
loudness_stats_source = "ebu_pass2"
|
|
273
|
+
loudnorm_stats = audio_streams[0].loudness_statistics.get("ebu_pass2")
|
|
274
|
+
|
|
275
|
+
if loudnorm_stats is None:
|
|
276
|
+
_logger.warning(
|
|
277
|
+
"ReplayGain: Second pass EBU statistics (ebu_pass2) not found. "
|
|
278
|
+
"Falling back to first pass EBU statistics (ebu_pass1). "
|
|
279
|
+
"This may not account for pre-filters if any are used."
|
|
280
|
+
)
|
|
281
|
+
loudness_stats_source = "ebu_pass1"
|
|
282
|
+
loudnorm_stats = audio_streams[0].loudness_statistics.get("ebu_pass1")
|
|
238
283
|
|
|
239
284
|
if loudnorm_stats is None:
|
|
240
|
-
_logger.error(
|
|
285
|
+
_logger.error(
|
|
286
|
+
f"ReplayGain: No loudness statistics available from {loudness_stats_source} (and fallback) for stream 0. "
|
|
287
|
+
"Cannot calculate ReplayGain tags."
|
|
288
|
+
)
|
|
241
289
|
return
|
|
242
290
|
|
|
291
|
+
_logger.debug(
|
|
292
|
+
f"Using statistics from {loudness_stats_source} for ReplayGain calculation."
|
|
293
|
+
)
|
|
294
|
+
|
|
243
295
|
# apply the replaygain tag from the first audio stream (to all audio streams)
|
|
244
296
|
if len(audio_streams) > 1:
|
|
245
297
|
_logger.warning(
|
|
@@ -249,23 +301,31 @@ class MediaFile:
|
|
|
249
301
|
)
|
|
250
302
|
|
|
251
303
|
target_level = self.ffmpeg_normalize.target_level
|
|
252
|
-
|
|
253
|
-
|
|
304
|
+
# Use 'input_i' and 'input_tp' from the chosen stats.
|
|
305
|
+
# For ebu_pass2, these are measurements *after* pre-filter but *before* loudnorm adjustment.
|
|
306
|
+
input_i = loudnorm_stats.get("input_i")
|
|
307
|
+
input_tp = loudnorm_stats.get("input_tp")
|
|
254
308
|
|
|
255
309
|
if input_i is None or input_tp is None:
|
|
256
|
-
_logger.error(
|
|
310
|
+
_logger.error(
|
|
311
|
+
f"ReplayGain: 'input_i' or 'input_tp' missing from {loudness_stats_source} statistics. "
|
|
312
|
+
"Cannot calculate ReplayGain tags."
|
|
313
|
+
)
|
|
257
314
|
return
|
|
258
315
|
|
|
259
316
|
track_gain = -(input_i - target_level) # dB
|
|
260
317
|
track_peak = 10 ** (input_tp / 20) # linear scale
|
|
261
318
|
|
|
262
|
-
_logger.debug(f"Track gain: {track_gain} dB")
|
|
263
|
-
_logger.debug(f"Track peak: {track_peak}")
|
|
319
|
+
_logger.debug(f"Calculated Track gain: {track_gain:.2f} dB")
|
|
320
|
+
_logger.debug(f"Calculated Track peak: {track_peak:.2f}")
|
|
264
321
|
|
|
265
|
-
if not self.ffmpeg_normalize.dry_run:
|
|
322
|
+
if not self.ffmpeg_normalize.dry_run: # This uses the overall dry_run state
|
|
266
323
|
self._write_replaygain_tags(track_gain, track_peak)
|
|
267
324
|
else:
|
|
268
|
-
_logger.warning(
|
|
325
|
+
_logger.warning(
|
|
326
|
+
"Overall dry_run is enabled, not actually writing ReplayGain tags to the file. "
|
|
327
|
+
"Tag calculation based on available stats was performed."
|
|
328
|
+
)
|
|
269
329
|
|
|
270
330
|
def _write_replaygain_tags(self, track_gain: float, track_peak: float) -> None:
|
|
271
331
|
"""
|
|
@@ -544,6 +604,10 @@ class MediaFile:
|
|
|
544
604
|
yield 100
|
|
545
605
|
return
|
|
546
606
|
|
|
607
|
+
# track temp_dir for cleanup
|
|
608
|
+
temp_dir = None
|
|
609
|
+
temp_file = None
|
|
610
|
+
|
|
547
611
|
# special case: if output is a null device, write directly to it
|
|
548
612
|
if self.output_file == os.devnull:
|
|
549
613
|
cmd.append(self.output_file)
|
|
@@ -554,33 +618,35 @@ class MediaFile:
|
|
|
554
618
|
|
|
555
619
|
cmd_runner = CommandRunner()
|
|
556
620
|
try:
|
|
557
|
-
|
|
558
|
-
yield from cmd_runner.run_ffmpeg_command(cmd)
|
|
559
|
-
except Exception as e:
|
|
560
|
-
_logger.error(
|
|
561
|
-
f"Error while running command {shlex.join(cmd)}! Error: {e}"
|
|
562
|
-
)
|
|
563
|
-
raise e
|
|
564
|
-
else:
|
|
565
|
-
if self.output_file != os.devnull:
|
|
566
|
-
_logger.debug(
|
|
567
|
-
f"Moving temporary file from {temp_file} to {self.output_file}"
|
|
568
|
-
)
|
|
569
|
-
move(temp_file, self.output_file)
|
|
570
|
-
rmtree(temp_dir, ignore_errors=True)
|
|
621
|
+
yield from cmd_runner.run_ffmpeg_command(cmd)
|
|
571
622
|
except Exception as e:
|
|
572
|
-
|
|
573
|
-
rmtree(temp_dir, ignore_errors=True)
|
|
623
|
+
_logger.error(f"Error while running command {shlex.join(cmd)}! Error: {e}")
|
|
574
624
|
raise e
|
|
625
|
+
else:
|
|
626
|
+
# only move the temp file if it's not a null device and ReplayGain is not enabled!
|
|
627
|
+
if (
|
|
628
|
+
self.output_file != os.devnull
|
|
629
|
+
and temp_file
|
|
630
|
+
and not self.ffmpeg_normalize.replaygain
|
|
631
|
+
):
|
|
632
|
+
_logger.debug(
|
|
633
|
+
f"Moving temporary file from {temp_file} to {self.output_file}"
|
|
634
|
+
)
|
|
635
|
+
move(temp_file, self.output_file)
|
|
636
|
+
finally:
|
|
637
|
+
# clean up temp directory if it was created
|
|
638
|
+
if temp_dir and os.path.exists(temp_dir):
|
|
639
|
+
rmtree(temp_dir, ignore_errors=True)
|
|
575
640
|
|
|
576
641
|
output = cmd_runner.get_output()
|
|
577
642
|
# in the second pass, we do not normalize stream-by-stream, so we set the stats based on the
|
|
578
643
|
# overall output (which includes multiple loudnorm stats)
|
|
579
644
|
if self.ffmpeg_normalize.normalization_type == "ebu":
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
645
|
+
ebu_pass_2_stats = list(
|
|
646
|
+
AudioStream.prune_and_parse_loudnorm_output(output).values()
|
|
647
|
+
)
|
|
648
|
+
for idx, audio_stream in enumerate(self.streams["audio"].values()):
|
|
649
|
+
audio_stream.set_second_pass_stats(ebu_pass_2_stats[idx])
|
|
584
650
|
|
|
585
651
|
# warn if self.media_file.ffmpeg_normalize.dynamic == False and any of the second pass stats contain "normalization_type" == "dynamic"
|
|
586
652
|
if self.ffmpeg_normalize.dynamic is False:
|
|
@@ -65,6 +65,9 @@ class MediaStream:
|
|
|
65
65
|
self.media_file = media_file
|
|
66
66
|
self.stream_type = stream_type
|
|
67
67
|
self.stream_id = stream_id
|
|
68
|
+
_logger.debug(
|
|
69
|
+
f"Created MediaStream for {self.media_file.input_file}, {self.stream_type} stream {self.stream_id}"
|
|
70
|
+
)
|
|
68
71
|
|
|
69
72
|
def __repr__(self) -> str:
|
|
70
73
|
return (
|
|
@@ -175,6 +178,9 @@ class AudioStream(MediaStream):
|
|
|
175
178
|
Args:
|
|
176
179
|
stats (dict): The EBU loudness statistics.
|
|
177
180
|
"""
|
|
181
|
+
_logger.debug(
|
|
182
|
+
f"Setting second pass stats for stream {self.stream_id} from {stats}"
|
|
183
|
+
)
|
|
178
184
|
self.loudness_statistics["ebu_pass2"] = stats
|
|
179
185
|
|
|
180
186
|
def get_pcm_codec(self) -> str:
|
|
@@ -339,8 +345,9 @@ class AudioStream(MediaStream):
|
|
|
339
345
|
output (str): The output from ffmpeg.
|
|
340
346
|
|
|
341
347
|
Returns:
|
|
342
|
-
|
|
348
|
+
dict[int, EbuLoudnessStatistics]: The EBU loudness statistics.
|
|
343
349
|
"""
|
|
350
|
+
_logger.debug("Parsing loudnorm stats from output")
|
|
344
351
|
pruned_output = CommandRunner.prune_ffmpeg_progress_from_output(output)
|
|
345
352
|
output_lines = [line.strip() for line in pruned_output.split("\n")]
|
|
346
353
|
return AudioStream._parse_loudnorm_output(output_lines)
|
|
@@ -359,7 +366,7 @@ class AudioStream(MediaStream):
|
|
|
359
366
|
FFmpegNormalizeError: When the output could not be parsed.
|
|
360
367
|
|
|
361
368
|
Returns:
|
|
362
|
-
EbuLoudnessStatistics:
|
|
369
|
+
dict[int, EbuLoudnessStatistics]: stream index and the EBU loudness statistics, if found.
|
|
363
370
|
"""
|
|
364
371
|
result = dict[int, EbuLoudnessStatistics]()
|
|
365
372
|
stream_index = -1
|
|
@@ -421,6 +428,28 @@ class AudioStream(MediaStream):
|
|
|
421
428
|
Return second pass loudnorm filter options string for ffmpeg
|
|
422
429
|
"""
|
|
423
430
|
|
|
431
|
+
# In dynamic mode, we can do everything in one pass, and we do not have first pass stats
|
|
432
|
+
if self.media_file.ffmpeg_normalize.dynamic:
|
|
433
|
+
if not self.ffmpeg_normalize.sample_rate:
|
|
434
|
+
_logger.warning(
|
|
435
|
+
"In dynamic mode, the sample rate will automatically be set to 192 kHz by the loudnorm filter. "
|
|
436
|
+
"Specify -ar/--sample-rate to override it."
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
opts = {
|
|
440
|
+
"i": self.media_file.ffmpeg_normalize.target_level,
|
|
441
|
+
"lra": self.media_file.ffmpeg_normalize.loudness_range_target,
|
|
442
|
+
"tp": self.media_file.ffmpeg_normalize.true_peak,
|
|
443
|
+
"offset": self.media_file.ffmpeg_normalize.offset,
|
|
444
|
+
"linear": "false",
|
|
445
|
+
"print_format": "json",
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if self.media_file.ffmpeg_normalize.dual_mono:
|
|
449
|
+
opts["dual_mono"] = "true"
|
|
450
|
+
|
|
451
|
+
return "loudnorm=" + dict_to_filter_opts(opts)
|
|
452
|
+
|
|
424
453
|
if not self.loudness_statistics["ebu_pass1"]:
|
|
425
454
|
raise FFmpegNormalizeError(
|
|
426
455
|
"First pass not run, you must call parse_loudnorm_stats first"
|
|
@@ -433,7 +462,7 @@ class AudioStream(MediaStream):
|
|
|
433
462
|
)
|
|
434
463
|
self.loudness_statistics["ebu_pass1"]["input_i"] = 0
|
|
435
464
|
|
|
436
|
-
will_use_dynamic_mode = self.media_file.ffmpeg_normalize.dynamic
|
|
465
|
+
will_use_dynamic_mode: bool = self.media_file.ffmpeg_normalize.dynamic
|
|
437
466
|
|
|
438
467
|
if self.media_file.ffmpeg_normalize.keep_loudness_range_target:
|
|
439
468
|
_logger.debug(
|