ffmpeg-normalize 1.37.2__tar.gz → 1.37.4__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.2 → ffmpeg_normalize-1.37.4}/PKG-INFO +1 -1
  2. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/pyproject.toml +6 -13
  3. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/__main__.py +1 -1
  4. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/_media_file.py +101 -2
  5. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/LICENSE.md +0 -0
  6. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/README.md +0 -0
  7. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/__init__.py +0 -0
  8. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/_cmd_utils.py +0 -0
  9. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/_errors.py +0 -0
  10. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/_ffmpeg_normalize.py +0 -0
  11. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/_logger.py +0 -0
  12. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/_presets.py +0 -0
  13. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/_streams.py +0 -0
  14. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/data/presets/music.json +0 -0
  15. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/data/presets/podcast.json +0 -0
  16. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/src/ffmpeg_normalize/data/presets/streaming-video.json +0 -0
  17. {ffmpeg_normalize-1.37.2 → ffmpeg_normalize-1.37.4}/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.2
3
+ Version: 1.37.4
4
4
  Summary: Normalize audio via ffmpeg
5
5
  Keywords: ffmpeg,normalize,audio
6
6
  Author: Werner Robitza
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["uv_build>=0.8.14,<0.9.0"]
2
+ requires = ["uv_build>=0.8.14,<1.0.0"]
3
3
  build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "ffmpeg-normalize"
7
- version = "1.37.2"
7
+ version = "1.37.4"
8
8
  description = "Normalize audio via ffmpeg"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -52,19 +52,12 @@ package-data = {"ffmpeg_normalize" = ["data/**/*.json"]}
52
52
  dev = [
53
53
  "pytest>=8.1.1,<9",
54
54
  "ruff>=0.12.11",
55
- "mypy>=1.0.0",
56
55
  "types-tqdm",
57
56
  "shtab>=1.7.0",
57
+ "ty>=0.0.24",
58
58
  ]
59
59
 
60
- [tool.mypy]
61
- python_version = "3.13"
62
- ignore_missing_imports = true
63
- exclude = ["build"]
64
- namespace_packages = false
65
- no_implicit_optional = true
66
- check_untyped_defs = true
67
- warn_return_any = true
68
- warn_unused_ignores = true
69
- show_error_codes = true
60
+ [tool.ty.rules]
61
+ invalid-argument-type = "warn"
62
+ too-many-positional-arguments = "warn"
70
63
 
@@ -197,7 +197,7 @@ def create_parser() -> argparse.ArgumentParser:
197
197
  help=textwrap.dedent(
198
198
  """\
199
199
  Write ReplayGain tags to the original file without normalizing.
200
- This mode will overwrite the input file and ignore other options.
200
+ This mode will overwrite the input file and ignore other options. It will strip all ReplayGain tags.
201
201
  Only works with EBU normalization, and only with .mp3, .mp4/.m4a, .ogg, .opus for now.
202
202
  """
203
203
  ),
@@ -332,8 +332,10 @@ class MediaFile:
332
332
  "ReplayGain tagging is enabled. Proceeding with tag calculation/application."
333
333
  )
334
334
  self._run_replaygain()
335
-
336
- if not self.ffmpeg_normalize.replaygain:
335
+ else:
336
+ # Strip any existing ReplayGain tags from the output file
337
+ # since they are no longer accurate after normalization
338
+ self._strip_replaygain_tags(self.output_file)
337
339
  _logger.info(f"Normalized file written to {self.output_file}")
338
340
 
339
341
  def _run_replaygain(self) -> None:
@@ -461,6 +463,103 @@ class MediaFile:
461
463
  f"Successfully wrote replaygain tags to input file {self.input_file}"
462
464
  )
463
465
 
466
+ def _strip_replaygain_tags(self, output_file: str) -> None:
467
+ """
468
+ Strip ReplayGain tags from the output file after normalization.
469
+
470
+ This ensures that old ReplayGain tags from the input are removed,
471
+ since they are no longer accurate after normalization.
472
+
473
+ Args:
474
+ output_file (str): Path to the output file to strip tags from
475
+ """
476
+ _logger.debug(f"Stripping ReplayGain tags from {output_file}")
477
+
478
+ output_file_ext = os.path.splitext(output_file)[1]
479
+ if output_file_ext == ".mp3":
480
+ try:
481
+ mp3 = MP3(output_file, ID3=ID3)
482
+ if not mp3.tags:
483
+ return
484
+ # Remove REPLAYGAIN_* tags
485
+ tags_to_remove = [
486
+ key for key in mp3.tags if key.startswith("TXXX:REPLAYGAIN_")
487
+ ]
488
+ for tag in tags_to_remove:
489
+ del mp3.tags[tag]
490
+ if tags_to_remove:
491
+ mp3.save()
492
+ _logger.debug(
493
+ f"Stripped {len(tags_to_remove)} ReplayGain tag(s) from {output_file}"
494
+ )
495
+ except Exception as e:
496
+ _logger.warning(
497
+ f"Could not strip ReplayGain tags from {output_file}: {e}"
498
+ )
499
+ elif output_file_ext in [".mp4", ".m4a", ".m4v", ".mov"]:
500
+ try:
501
+ mp4 = MP4(output_file)
502
+ if not mp4.tags:
503
+ return
504
+ # Remove REPLAYGAIN_* tags
505
+ tags_to_remove = [
506
+ key
507
+ for key in mp4.tags
508
+ if "REPLAYGAIN_" in key.upper() or "REPLAYGAIN" in key.upper()
509
+ ]
510
+ for tag in tags_to_remove:
511
+ del mp4.tags[tag]
512
+ if tags_to_remove:
513
+ mp4.save()
514
+ _logger.debug(
515
+ f"Stripped {len(tags_to_remove)} ReplayGain tag(s) from {output_file}"
516
+ )
517
+ except Exception as e:
518
+ _logger.warning(
519
+ f"Could not strip ReplayGain tags from {output_file}: {e}"
520
+ )
521
+ elif output_file_ext == ".ogg":
522
+ try:
523
+ ogg = OggVorbis(output_file)
524
+ # Remove REPLAYGAIN_* tags (case-insensitive)
525
+ tags_to_remove = [
526
+ key for key in ogg.keys() if key.upper().startswith("REPLAYGAIN_")
527
+ ]
528
+ for tag in tags_to_remove:
529
+ del ogg[tag]
530
+ if tags_to_remove:
531
+ ogg.save()
532
+ _logger.debug(
533
+ f"Stripped {len(tags_to_remove)} ReplayGain tag(s) from {output_file}"
534
+ )
535
+ except Exception as e:
536
+ _logger.warning(
537
+ f"Could not strip ReplayGain tags from {output_file}: {e}"
538
+ )
539
+ elif output_file_ext == ".opus":
540
+ try:
541
+ opus = OggOpus(output_file)
542
+ # Remove R128_* tags (Opus uses R128 instead of REPLAYGAIN, case-insensitive)
543
+ tags_to_remove = [
544
+ key for key in opus.keys() if key.upper().startswith("R128_")
545
+ ]
546
+ for tag in tags_to_remove:
547
+ del opus[tag]
548
+ if tags_to_remove:
549
+ opus.save()
550
+ _logger.debug(
551
+ f"Stripped {len(tags_to_remove)} R128 tag(s) from {output_file}"
552
+ )
553
+ except Exception as e:
554
+ _logger.warning(
555
+ f"Could not strip ReplayGain tags from {output_file}: {e}"
556
+ )
557
+ else:
558
+ # For other formats, we don't need to strip tags
559
+ _logger.debug(
560
+ f"Skipping ReplayGain tag stripping for {output_file_ext} (not supported/needed)"
561
+ )
562
+
464
563
  def _can_write_output_video(self) -> bool:
465
564
  """
466
565
  Determine whether the output file can contain video at all.