ffmpeg-normalize 1.37.4__tar.gz → 1.37.6__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.
Files changed (17) hide show
  1. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/PKG-INFO +1 -1
  2. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/pyproject.toml +1 -1
  3. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/_media_file.py +37 -4
  4. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/_streams.py +32 -6
  5. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/LICENSE.md +0 -0
  6. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/README.md +0 -0
  7. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/__init__.py +0 -0
  8. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/__main__.py +0 -0
  9. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/_cmd_utils.py +0 -0
  10. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/_errors.py +0 -0
  11. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/_ffmpeg_normalize.py +0 -0
  12. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/_logger.py +0 -0
  13. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/_presets.py +0 -0
  14. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/data/presets/music.json +0 -0
  15. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/data/presets/podcast.json +0 -0
  16. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/data/presets/streaming-video.json +0 -0
  17. {ffmpeg_normalize-1.37.4 → ffmpeg_normalize-1.37.6}/src/ffmpeg_normalize/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ffmpeg-normalize
3
- Version: 1.37.4
3
+ Version: 1.37.6
4
4
  Summary: Normalize audio via ffmpeg
5
5
  Keywords: ffmpeg,normalize,audio
6
6
  Author: Werner Robitza
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "ffmpeg-normalize"
7
- version = "1.37.4"
7
+ version = "1.37.6"
8
8
  description = "Normalize audio via ffmpeg"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -880,17 +880,50 @@ class MediaFile:
880
880
  "This can happen when normalization is skipped (e.g., with --lower-only)."
881
881
  )
882
882
 
883
- # warn if self.media_file.ffmpeg_normalize.dynamic == False and any of the second pass stats contain "normalization_type" == "dynamic"
883
+ # warn if dynamic == False and any of the second pass stats contain "normalization_type" == "dynamic"
884
884
  if self.ffmpeg_normalize.dynamic is False:
885
885
  for audio_stream in self.streams["audio"].values():
886
886
  pass2_stats = audio_stream.get_stats()["ebu_pass2"]
887
887
  if pass2_stats is None:
888
888
  continue
889
889
  if pass2_stats["normalization_type"] == "dynamic":
890
+ pass1_stats = audio_stream.get_stats()["ebu_pass1"]
891
+
892
+ reason = ""
893
+ if pass1_stats is not None:
894
+ linear_gain = (
895
+ self.ffmpeg_normalize.target_level - pass1_stats["input_i"]
896
+ )
897
+ estimated_tp = pass1_stats["input_tp"] + linear_gain
898
+ if estimated_tp > self.ffmpeg_normalize.true_peak:
899
+ min_tp = estimated_tp
900
+ max_target = (
901
+ self.ffmpeg_normalize.true_peak
902
+ - pass1_stats["input_tp"]
903
+ + pass1_stats["input_i"]
904
+ )
905
+ reason = (
906
+ f" Reason: the input true peak ({pass1_stats['input_tp']:.2f} dBTP) is too high — "
907
+ f"after linear gain of {linear_gain:.2f} dB, "
908
+ f"the estimated true peak would be {estimated_tp:.2f} dBTP, "
909
+ f"exceeding the target of {self.ffmpeg_normalize.true_peak} dBTP. "
910
+ f"To avoid this, raise --true-peak (-tp) to at least {min_tp:.1f}, "
911
+ f"or lower the target level (-t) to at most {max_target:.1f}."
912
+ )
913
+ elif (
914
+ pass1_stats["input_lra"]
915
+ > self.ffmpeg_normalize.loudness_range_target
916
+ ):
917
+ reason = (
918
+ f" Reason: the input loudness range ({pass1_stats['input_lra']:.2f} LU) "
919
+ f"exceeds the target ({self.ffmpeg_normalize.loudness_range_target:.2f} LU). "
920
+ "Consider raising the target loudness range or using "
921
+ "--keep-loudness-range-target / --keep-lra-above-loudness-range-target."
922
+ )
923
+
890
924
  _logger.warning(
891
- "You specified linear normalization, but the loudnorm filter reverted to dynamic normalization. "
892
- "This may lead to unexpected results."
893
- "Consider your input settings, e.g. choose a lower target level or higher target loudness range."
925
+ f"{self.input_file}: You specified linear normalization, but the loudnorm filter "
926
+ f"reverted to dynamic normalization.{reason}"
894
927
  )
895
928
 
896
929
  _logger.debug("Normalization finished")
@@ -201,7 +201,7 @@ class AudioStream(MediaStream):
201
201
  return f"pcm_s{self.bit_depth}le"
202
202
  else:
203
203
  _logger.warning(
204
- f"Unsupported bit depth {self.bit_depth}, falling back to pcm_s16le"
204
+ f"{self.media_file.input_file}: Unsupported bit depth {self.bit_depth}, falling back to pcm_s16le"
205
205
  )
206
206
  return "pcm_s16le"
207
207
 
@@ -483,7 +483,7 @@ class AudioStream(MediaStream):
483
483
 
484
484
  if float(self.loudness_statistics["ebu_pass1"]["input_i"]) > 0:
485
485
  _logger.warning(
486
- "Input file had measured input loudness greater than zero "
486
+ f"{self.media_file.input_file}: Input file had measured input loudness greater than zero "
487
487
  f"({self.loudness_statistics['ebu_pass1']['input_i']}), capping at 0"
488
488
  )
489
489
  self.loudness_statistics["ebu_pass1"]["input_i"] = 0
@@ -497,7 +497,7 @@ class AudioStream(MediaStream):
497
497
  input_lra = self.loudness_statistics["ebu_pass1"]["input_lra"]
498
498
  if input_lra < 1 or input_lra > 50:
499
499
  _logger.warning(
500
- "Input file had measured loudness range outside of [1,50] "
500
+ f"{self.media_file.input_file}: Input file had measured loudness range outside of [1,50] "
501
501
  f"({input_lra}), capping to allowed range"
502
502
  )
503
503
 
@@ -527,7 +527,7 @@ class AudioStream(MediaStream):
527
527
  and not will_use_dynamic_mode
528
528
  ):
529
529
  _logger.warning(
530
- f"Input file had loudness range of {self.loudness_statistics['ebu_pass1']['input_lra']}. "
530
+ f"{self.media_file.input_file}: Input file had loudness range of {self.loudness_statistics['ebu_pass1']['input_lra']}. "
531
531
  f"This is larger than the loudness range target ({self.media_file.ffmpeg_normalize.loudness_range_target}). "
532
532
  "Normalization will revert to dynamic mode. Choose a higher target loudness range if you want linear normalization. "
533
533
  "Alternatively, use the --keep-loudness-range-target or --keep-lra-above-loudness-range-target option to keep the target loudness range from "
@@ -552,11 +552,35 @@ class AudioStream(MediaStream):
552
552
  if safe_target < self.ffmpeg_normalize.target_level:
553
553
  target_level = safe_target
554
554
  _logger.warning(
555
- f"Using loudness target {target_level} because --auto-lower-loudness-target given.",
555
+ f"{self.media_file.input_file}: Using loudness target {target_level} because --auto-lower-loudness-target given.",
556
556
  )
557
557
 
558
558
  stats = self.loudness_statistics["ebu_pass1"]
559
559
 
560
+ # Check if the true peak constraint will force dynamic mode.
561
+ # In linear mode, a uniform gain of (target - input_i) is applied.
562
+ # If that would leave the true peak above the TP limit, loudnorm
563
+ # cannot satisfy both constraints linearly and will fall back to
564
+ # dynamic processing.
565
+ if not will_use_dynamic_mode:
566
+ linear_gain = target_level - stats["input_i"]
567
+ estimated_tp = stats["input_tp"] + linear_gain
568
+ if estimated_tp > self.media_file.ffmpeg_normalize.true_peak:
569
+ min_tp = estimated_tp
570
+ max_target = (
571
+ self.media_file.ffmpeg_normalize.true_peak
572
+ - stats["input_tp"]
573
+ + stats["input_i"]
574
+ )
575
+ _logger.warning(
576
+ f"{self.media_file.input_file}: Linear normalization would result in an estimated true peak of "
577
+ f"{estimated_tp:.2f} dBTP (input true peak {stats['input_tp']:.2f} dBTP + gain of {linear_gain:.2f} dB), "
578
+ f"which exceeds the target true peak of {self.media_file.ffmpeg_normalize.true_peak} dBTP. "
579
+ "The loudnorm filter will likely use dynamic mode instead. "
580
+ f"To avoid this, raise --true-peak (-tp) to at least {min_tp:.1f}, "
581
+ f"or lower the target level (-t) to at most {max_target:.1f}."
582
+ )
583
+
560
584
  # Adjust target level for batch mode to preserve relative loudness
561
585
  if batch_reference is not None:
562
586
  input_i = float(stats["input_i"])
@@ -654,6 +678,8 @@ class AudioStream(MediaStream):
654
678
 
655
679
  clip_amount = self.loudness_statistics["max"] + adjustment
656
680
  if clip_amount > 0:
657
- _logger.warning(f"Adjusting will lead to clipping of {clip_amount} dB")
681
+ _logger.warning(
682
+ f"{self.media_file.input_file}: Adjusting will lead to clipping of {clip_amount} dB"
683
+ )
658
684
 
659
685
  return f"volume={adjustment}dB"