ffmpeg-normalize 1.37.8__tar.gz → 1.38.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.
Files changed (17) hide show
  1. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/PKG-INFO +3 -1
  2. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/README.md +2 -0
  3. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/pyproject.toml +1 -1
  4. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/__main__.py +4 -3
  5. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/_media_file.py +31 -26
  6. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/LICENSE.md +0 -0
  7. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/__init__.py +0 -0
  8. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/_cmd_utils.py +0 -0
  9. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/_errors.py +0 -0
  10. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/_ffmpeg_normalize.py +0 -0
  11. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/_logger.py +0 -0
  12. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/_presets.py +0 -0
  13. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/_streams.py +0 -0
  14. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/data/presets/music.json +0 -0
  15. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/data/presets/podcast.json +0 -0
  16. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/src/ffmpeg_normalize/data/presets/streaming-video.json +0 -0
  17. {ffmpeg_normalize-1.37.8 → ffmpeg_normalize-1.38.0}/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.8
3
+ Version: 1.38.0
4
4
  Summary: Normalize audio via ffmpeg
5
5
  Keywords: ffmpeg,normalize,audio
6
6
  Author: Werner Robitza
@@ -64,6 +64,8 @@ This program normalizes media files to a certain loudness level using the EBU R1
64
64
 
65
65
  ## 🆕 What's New
66
66
 
67
+ - Version 1.38.0 writes the normalized output directly to the destination without using temporary files
68
+
67
69
  - Version 1.36.0 introduces **presets** with `--preset`! Save and reuse your favorite normalization configurations for different use cases. Comes with three built-in presets: `podcast` (AES standard), `music` (RMS-based batch normalization), and `streaming-video` (video content). Create custom presets too!
68
70
 
69
71
  Example:
@@ -33,6 +33,8 @@ This program normalizes media files to a certain loudness level using the EBU R1
33
33
 
34
34
  ## 🆕 What's New
35
35
 
36
+ - Version 1.38.0 writes the normalized output directly to the destination without using temporary files
37
+
36
38
  - Version 1.36.0 introduces **presets** with `--preset`! Save and reuse your favorite normalization configurations for different use cases. Comes with three built-in presets: `podcast` (AES standard), `music` (RMS-based batch normalization), and `streaming-video` (video content). Create custom presets too!
37
39
 
38
40
  Example:
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "ffmpeg-normalize"
7
- version = "1.37.8"
7
+ version = "1.38.0"
8
8
  description = "Normalize audio via ffmpeg"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+
5
+ # Import version from package
6
+ import importlib.metadata
4
7
  import json
5
8
  import logging
6
9
  import os
@@ -15,9 +18,6 @@ from ._ffmpeg_normalize import NORMALIZATION_TYPES, FFmpegNormalize
15
18
  from ._logger import setup_cli_logger
16
19
  from ._presets import PresetManager
17
20
 
18
- # Import version from package
19
- import importlib.metadata
20
-
21
21
  __version__ = importlib.metadata.version("ffmpeg-normalize")
22
22
 
23
23
  _logger = logging.getLogger(__name__)
@@ -42,6 +42,7 @@ def create_parser() -> argparse.ArgumentParser:
42
42
  - `TMP` / `TEMP` / `TMPDIR`
43
43
  Sets the path to the temporary directory in which files are
44
44
  stored before being moved to the final output directory.
45
+ Only valid for ReplayGain and when the input file == output file.
45
46
  Note: You need to use full paths.
46
47
 
47
48
  - `FFMPEG_PATH`
@@ -4,8 +4,8 @@ import logging
4
4
  import os
5
5
  import re
6
6
  import shlex
7
- from shutil import move, rmtree
8
- from tempfile import mkdtemp
7
+ from shutil import rmtree
8
+ from tempfile import mkdtemp, mkstemp
9
9
  from typing import TYPE_CHECKING, Iterable, Iterator, Literal, TypedDict, Union
10
10
 
11
11
  from mutagen.id3 import ID3, TXXX
@@ -297,14 +297,14 @@ class MediaFile:
297
297
  f"Batch mode: Skipping first pass (already completed), using batch reference = {batch_reference:.2f}"
298
298
  )
299
299
 
300
- # for second pass, create a temp file
301
- temp_dir = mkdtemp()
302
- self.temp_file = os.path.join(temp_dir, f"out.{self.output_ext}")
300
+ temp_dir = None
303
301
 
304
302
  if self.ffmpeg_normalize.replaygain:
305
303
  _logger.debug(
306
304
  "ReplayGain mode: Second pass will run with temporary file to get stats."
307
305
  )
306
+ temp_dir = mkdtemp()
307
+ self.temp_file = os.path.join(temp_dir, f"out.{self.output_ext}")
308
308
  self.output_file = self.temp_file
309
309
 
310
310
  # run the second pass as a whole.
@@ -322,7 +322,7 @@ class MediaFile:
322
322
  pass
323
323
 
324
324
  # remove temp dir; this will remove the temp file as well if it has not been renamed (e.g. for replaygain)
325
- if os.path.exists(temp_dir):
325
+ if temp_dir and os.path.exists(temp_dir):
326
326
  rmtree(temp_dir, ignore_errors=True)
327
327
 
328
328
  # This will use stats from ebu_pass2 if available (from the main second pass),
@@ -836,39 +836,44 @@ class MediaFile:
836
836
  yield 100
837
837
  return
838
838
 
839
- # track temp_dir for cleanup
840
- temp_dir = None
841
- temp_file = None
839
+ is_in_place_overwrite = os.path.realpath(self.input_file) == os.path.realpath(
840
+ self.output_file
841
+ )
842
842
 
843
- # special case: if output is a null device, write directly to it
844
- if self.output_file == os.devnull:
845
- cmd.append(self.output_file)
846
- else:
847
- temp_dir = mkdtemp()
848
- temp_file = os.path.join(temp_dir, f"out.{self.output_ext}")
843
+ temp_file: Union[str, None] = None
844
+ if is_in_place_overwrite:
845
+ # need to create a temporary file because we cannot override
846
+ # the same input file
847
+ output_dir = os.path.dirname(self.output_file) or "."
848
+ fd, temp_file = mkstemp(
849
+ suffix=f".{self.output_ext}",
850
+ prefix=f".{os.path.splitext(os.path.basename(self.output_file))[0]}.",
851
+ dir=output_dir,
852
+ )
853
+ os.close(fd)
854
+ _logger.debug(
855
+ f"Output file is the same as the input file, "
856
+ f"encoding to temporary file {temp_file} first"
857
+ )
849
858
  cmd.append(temp_file)
859
+ else:
860
+ cmd.append(self.output_file)
850
861
 
851
862
  cmd_runner = CommandRunner()
852
863
  try:
853
864
  yield from cmd_runner.run_ffmpeg_command(cmd)
854
865
  except Exception as e:
855
866
  _logger.error(f"Error while running command {shlex.join(cmd)}! Error: {e}")
867
+ if temp_file and os.path.exists(temp_file):
868
+ os.remove(temp_file)
856
869
  raise e
857
870
  else:
858
- # only move the temp file if it's not a null device and ReplayGain is not enabled!
859
- if (
860
- self.output_file != os.devnull
861
- and temp_file
862
- and not self.ffmpeg_normalize.replaygain
863
- ):
871
+ # for in-place normalization, move the finished temp file over the input
872
+ if temp_file:
864
873
  _logger.debug(
865
874
  f"Moving temporary file from {temp_file} to {self.output_file}"
866
875
  )
867
- move(temp_file, self.output_file)
868
- finally:
869
- # clean up temp directory if it was created
870
- if temp_dir and os.path.exists(temp_dir):
871
- rmtree(temp_dir, ignore_errors=True)
876
+ os.replace(temp_file, self.output_file)
872
877
 
873
878
  output = cmd_runner.get_output()
874
879
  # in the second pass, we do not normalize stream-by-stream, so we set the stats based on the