ffmpeg-normalize 1.32.2__py2.py3-none-any.whl → 1.32.4__py2.py3-none-any.whl

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.
@@ -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): Dynamic. Defaults to False.
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.
@@ -207,8 +207,16 @@ class MediaFile:
207
207
  """
208
208
  _logger.debug(f"Running normalization for {self.input_file}")
209
209
 
210
- # run the first pass to get loudness stats
211
- self._first_pass()
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
+ )
212
220
 
213
221
  # for second pass, create a temp file
214
222
  temp_dir = mkdtemp()
@@ -596,6 +604,10 @@ class MediaFile:
596
604
  yield 100
597
605
  return
598
606
 
607
+ # track temp_dir for cleanup
608
+ temp_dir = None
609
+ temp_file = None
610
+
599
611
  # special case: if output is a null device, write directly to it
600
612
  if self.output_file == os.devnull:
601
613
  cmd.append(self.output_file)
@@ -612,11 +624,15 @@ class MediaFile:
612
624
  raise e
613
625
  else:
614
626
  # only move the temp file if it's not a null device and ReplayGain is not enabled!
615
- if self.output_file != os.devnull and not self.ffmpeg_normalize.replaygain:
627
+ if self.output_file != os.devnull and temp_file and not self.ffmpeg_normalize.replaygain:
616
628
  _logger.debug(
617
629
  f"Moving temporary file from {temp_file} to {self.output_file}"
618
630
  )
619
631
  move(temp_file, self.output_file)
632
+ finally:
633
+ # clean up temp directory if it was created
634
+ if temp_dir and os.path.exists(temp_dir):
635
+ rmtree(temp_dir, ignore_errors=True)
620
636
 
621
637
  output = cmd_runner.get_output()
622
638
  # in the second pass, we do not normalize stream-by-stream, so we set the stats based on the
@@ -428,6 +428,28 @@ class AudioStream(MediaStream):
428
428
  Return second pass loudnorm filter options string for ffmpeg
429
429
  """
430
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
+
431
453
  if not self.loudness_statistics["ebu_pass1"]:
432
454
  raise FFmpegNormalizeError(
433
455
  "First pass not run, you must call parse_loudnorm_stats first"
@@ -1 +1 @@
1
- __version__ = "1.32.2"
1
+ __version__ = "1.32.4"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
2
- Name: ffmpeg-normalize
3
- Version: 1.32.2
1
+ Metadata-Version: 2.4
2
+ Name: ffmpeg_normalize
3
+ Version: 1.32.4
4
4
  Summary: Normalize audio via ffmpeg
5
5
  Home-page: https://github.com/slhck/ffmpeg-normalize
6
6
  Author: Werner Robitza
@@ -24,10 +24,22 @@ Requires-Python: >=3.9
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: tqdm
27
+ Requires-Dist: colorama; platform_system == "Windows"
27
28
  Requires-Dist: ffmpeg-progress-yield
28
29
  Requires-Dist: colorlog
29
30
  Requires-Dist: mutagen
30
- Requires-Dist: colorama; platform_system == "Windows"
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: classifier
34
+ Dynamic: description
35
+ Dynamic: description-content-type
36
+ Dynamic: home-page
37
+ Dynamic: keywords
38
+ Dynamic: license
39
+ Dynamic: license-file
40
+ Dynamic: requires-dist
41
+ Dynamic: requires-python
42
+ Dynamic: summary
31
43
 
32
44
  # ffmpeg-normalize
33
45
 
@@ -45,7 +57,7 @@ This program normalizes media files to a certain loudness level using the EBU R1
45
57
 
46
58
  ## ✨ Features
47
59
 
48
- - EBU R128 loudness normalization
60
+ - EBU R128 loudness normalization (two-pass by default, with an option for one-pass dynamic normalization)
49
61
  - RMS-based normalization
50
62
  - Peak normalization
51
63
  - Video file support
@@ -119,6 +131,22 @@ The only reason this project exists in its current form is because [@benjaoming]
119
131
  # Changelog
120
132
 
121
133
 
134
+ ## v1.32.4 (2025-06-08)
135
+
136
+ * Update api docs.
137
+
138
+ * Update docs.
139
+
140
+ * Make --dynamic option use one pass only, fixes #263.
141
+
142
+ * Fix type error.
143
+
144
+
145
+ ## v1.32.3 (2025-05-30)
146
+
147
+ * Fix temp dir cleanup, fixes #284.
148
+
149
+
122
150
  ## v1.32.2 (2025-05-08)
123
151
 
124
152
  * Docs: reference changelog.
@@ -0,0 +1,16 @@
1
+ ffmpeg_normalize/__init__.py,sha256=aAhlk93ZE6SQcWUDzZQcw9vJh0bJcKEUNFGhVc5ZIto,453
2
+ ffmpeg_normalize/__main__.py,sha256=LMj4Xl140ZKe5naY36fAwzz72lK59srzkVlbb0KzkcQ,19955
3
+ ffmpeg_normalize/_cmd_utils.py,sha256=iGzO3iOylDUOnx-FCKd84BMxiIhmIthxU1tg7kvf4Ss,5269
4
+ ffmpeg_normalize/_errors.py,sha256=brTQ4osJ4fTA8wnyMPVVYfGwJ0wqeShRFydTEwi_VEY,48
5
+ ffmpeg_normalize/_ffmpeg_normalize.py,sha256=_ZK2P3kAM0mnxY3iCYH61T6jhMndTdO2Rqh03vJo7rY,11852
6
+ ffmpeg_normalize/_logger.py,sha256=3Ap4Fxg7xGrzz7h4IGuNEf0KKstx0Rq_eLbHPrHzcrI,1841
7
+ ffmpeg_normalize/_media_file.py,sha256=6QYGIlNFpxojYjof_3y1HoU8SgsVnMor2y4hx-22CVY,26323
8
+ ffmpeg_normalize/_streams.py,sha256=tg4D5UMBFpPFxK5x-HG6mwgqkm9Z6H1kH14OlaCXM-A,21590
9
+ ffmpeg_normalize/_version.py,sha256=kVLezpNpKw6uZIvSwisQSFv3H0SHTGxoH32YSRqVSpQ,23
10
+ ffmpeg_normalize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ ffmpeg_normalize-1.32.4.dist-info/licenses/LICENSE,sha256=mw5RQE6v4UXG_d2gYIQw9rq6jYWQCtzIs3fSm5sBSrs,1076
12
+ ffmpeg_normalize-1.32.4.dist-info/METADATA,sha256=kEvnRedCUL1slT6bbN7m--Hhpgw7yvixyrULe-f2MPQ,33285
13
+ ffmpeg_normalize-1.32.4.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
14
+ ffmpeg_normalize-1.32.4.dist-info/entry_points.txt,sha256=X0EC5ptb0iGOxrk3Aa65dVQtvUixngLd_2-iAtSixdc,68
15
+ ffmpeg_normalize-1.32.4.dist-info/top_level.txt,sha256=wnUkr17ckPrrU1JsxZQiXbEBUnHKsC64yck-MemEBuI,17
16
+ ffmpeg_normalize-1.32.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,16 +0,0 @@
1
- ffmpeg_normalize/__init__.py,sha256=aAhlk93ZE6SQcWUDzZQcw9vJh0bJcKEUNFGhVc5ZIto,453
2
- ffmpeg_normalize/__main__.py,sha256=LMj4Xl140ZKe5naY36fAwzz72lK59srzkVlbb0KzkcQ,19955
3
- ffmpeg_normalize/_cmd_utils.py,sha256=iGzO3iOylDUOnx-FCKd84BMxiIhmIthxU1tg7kvf4Ss,5269
4
- ffmpeg_normalize/_errors.py,sha256=brTQ4osJ4fTA8wnyMPVVYfGwJ0wqeShRFydTEwi_VEY,48
5
- ffmpeg_normalize/_ffmpeg_normalize.py,sha256=79wzFR4ZOgy-Wn0ywi8PNJzsrxiDSMKtJ6_auHxiQvo,11762
6
- ffmpeg_normalize/_logger.py,sha256=3Ap4Fxg7xGrzz7h4IGuNEf0KKstx0Rq_eLbHPrHzcrI,1841
7
- ffmpeg_normalize/_media_file.py,sha256=E1PugUYf-SyOAbtPkJ3UHNfrJR3Ed5KaO8OOT9UHh8k,25737
8
- ffmpeg_normalize/_streams.py,sha256=w-gzAFUbnoLiRABckUgYqdhVgidtEATNd3di-jiP9fU,20599
9
- ffmpeg_normalize/_version.py,sha256=WdfJcWchCw6txwQmjH_WAwImubvfgUZlGyLcv7_k8f4,23
10
- ffmpeg_normalize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- ffmpeg_normalize-1.32.2.dist-info/LICENSE,sha256=mw5RQE6v4UXG_d2gYIQw9rq6jYWQCtzIs3fSm5sBSrs,1076
12
- ffmpeg_normalize-1.32.2.dist-info/METADATA,sha256=mZweBzs3rrgkjaVbr3H3jzaU4H3bMUO8MN0COu15ZTM,32758
13
- ffmpeg_normalize-1.32.2.dist-info/WHEEL,sha256=fS9sRbCBHs7VFcwJLnLXN1MZRR0_TVTxvXKzOnaSFs8,110
14
- ffmpeg_normalize-1.32.2.dist-info/entry_points.txt,sha256=X0EC5ptb0iGOxrk3Aa65dVQtvUixngLd_2-iAtSixdc,68
15
- ffmpeg_normalize-1.32.2.dist-info/top_level.txt,sha256=wnUkr17ckPrrU1JsxZQiXbEBUnHKsC64yck-MemEBuI,17
16
- ffmpeg_normalize-1.32.2.dist-info/RECORD,,