ffmpeg-normalize 1.34.0__py3-none-any.whl → 1.35.0__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.
@@ -179,6 +179,24 @@ def create_parser() -> argparse.ArgumentParser:
179
179
  """
180
180
  ),
181
181
  )
182
+ group_normalization.add_argument(
183
+ "--batch",
184
+ action="store_true",
185
+ help=textwrap.dedent(
186
+ """\
187
+ Preserve relative loudness between files (album mode).
188
+
189
+ When operating on a group of unrelated files, you usually want all of them at the same
190
+ level. However, a group of music files all from the same album is generally meant to be
191
+ listened to at the relative volumes they were recorded at. In batch mode, all the specified
192
+ files are considered to be part of a single album and their relative volumes are preserved.
193
+ This is done by averaging the loudness of all the files, computing a single adjustment from
194
+ that, and applying a relative adjustment to all the files.
195
+
196
+ Batch mode works with all normalization types (EBU, RMS, peak).
197
+ """
198
+ ),
199
+ )
182
200
 
183
201
  # group_normalization.add_argument(
184
202
  # '--threshold',
@@ -636,6 +654,7 @@ def main() -> None:
636
654
  dry_run=cli_args.dry_run,
637
655
  progress=cli_args.progress,
638
656
  replaygain=cli_args.replaygain,
657
+ batch=cli_args.batch,
639
658
  audio_streams=audio_streams,
640
659
  audio_default_only=cli_args.audio_default_only,
641
660
  keep_other_audio=cli_args.keep_other_audio,
@@ -84,6 +84,7 @@ class FFmpegNormalize:
84
84
  debug (bool, optional): Debug. Defaults to False.
85
85
  progress (bool, optional): Progress. Defaults to False.
86
86
  replaygain (bool, optional): Write ReplayGain tags without normalizing. Defaults to False.
87
+ batch (bool, optional): Preserve relative loudness between files (album mode). Defaults to False.
87
88
  audio_streams (list[int] | None, optional): List of audio stream indices to normalize. Defaults to None (all streams).
88
89
  audio_default_only (bool, optional): Only normalize audio streams with default disposition. Defaults to False.
89
90
  keep_other_audio (bool, optional): Keep non-selected audio streams in output (copy without normalization). Defaults to False.
@@ -127,6 +128,7 @@ class FFmpegNormalize:
127
128
  debug: bool = False,
128
129
  progress: bool = False,
129
130
  replaygain: bool = False,
131
+ batch: bool = False,
130
132
  audio_streams: list[int] | None = None,
131
133
  audio_default_only: bool = False,
132
134
  keep_other_audio: bool = False,
@@ -212,6 +214,7 @@ class FFmpegNormalize:
212
214
  self.debug = debug
213
215
  self.progress = progress
214
216
  self.replaygain = replaygain
217
+ self.batch = batch
215
218
 
216
219
  # Stream selection options
217
220
  self.audio_streams = audio_streams
@@ -272,29 +275,172 @@ class FFmpegNormalize:
272
275
  self.media_files.append(MediaFile(self, input_file, output_file))
273
276
  self.file_count += 1
274
277
 
278
+ def _calculate_batch_reference(self) -> float | None:
279
+ """
280
+ Calculate the batch reference loudness by averaging measurements across all files.
281
+
282
+ Returns:
283
+ float | None: The batch reference loudness value, or None if no measurements found.
284
+
285
+ Note:
286
+ TODO: Add option to specify different averaging methods (duration-weighted,
287
+ use quietest/loudest track, etc.)
288
+ """
289
+ measurements: list[float] = []
290
+
291
+ for media_file in self.media_files:
292
+ # Access audio streams from the streams dict
293
+ audio_streams = media_file.streams.get("audio", {})
294
+ for stream in audio_streams.values():
295
+ if self.normalization_type == "ebu":
296
+ # Get EBU integrated loudness from first pass
297
+ ebu_stats = stream.loudness_statistics.get("ebu_pass1")
298
+ if ebu_stats and "input_i" in ebu_stats:
299
+ measurements.append(float(ebu_stats["input_i"]))
300
+ elif self.normalization_type == "rms":
301
+ # Get RMS mean value
302
+ mean = stream.loudness_statistics.get("mean")
303
+ if mean is not None:
304
+ measurements.append(float(mean))
305
+ elif self.normalization_type == "peak":
306
+ # Get peak max value
307
+ max_val = stream.loudness_statistics.get("max")
308
+ if max_val is not None:
309
+ measurements.append(float(max_val))
310
+
311
+ if not measurements:
312
+ _logger.warning(
313
+ "No loudness measurements found for batch reference calculation. "
314
+ "Batch mode will not be applied."
315
+ )
316
+ return None
317
+
318
+ # Simple average of all measurements
319
+ batch_reference = sum(measurements) / len(measurements)
320
+ _logger.debug(f"Batch mode: Measurements for batch reference: {measurements}")
321
+ _logger.info(
322
+ f"Batch mode: Calculated reference loudness = {batch_reference:.2f} "
323
+ f"({self.normalization_type.upper()}, averaged from {len(measurements)} stream(s))"
324
+ )
325
+
326
+ return batch_reference
327
+
275
328
  def run_normalization(self) -> None:
276
329
  """
277
- Run the normalization procedures
330
+ Run the normalization procedures.
331
+
332
+ In batch mode, all files are analyzed first (first pass), then a batch reference
333
+ loudness is calculated, and finally all files are normalized (second pass) with
334
+ adjustments relative to the batch reference to preserve relative loudness.
335
+
336
+ In non-batch mode, each file is processed completely (both passes) before
337
+ moving to the next file.
278
338
  """
279
- for index, media_file in enumerate(
280
- tqdm(self.media_files, desc="File", disable=not self.progress, position=0)
281
- ):
339
+ if self.batch:
340
+ # Batch mode: analyze all files first, then normalize with relative adjustments
282
341
  _logger.info(
283
- f"Normalizing file {media_file} ({index + 1} of {self.file_count})"
342
+ f"Batch mode enabled: processing {self.file_count} file(s) while preserving relative loudness"
284
343
  )
285
344
 
286
- try:
287
- media_file.run_normalization()
288
- except Exception as e:
289
- if len(self.media_files) > 1:
290
- # simply warn and do not die
291
- _logger.error(
292
- f"Error processing input file {media_file}, will "
293
- f"continue batch-processing. Error was: {e}"
294
- )
295
- else:
296
- # raise the error so the program will exit
297
- raise e
345
+ # Recommend RMS/Peak for album normalization instead of EBU
346
+ if self.normalization_type == "ebu":
347
+ _logger.warning(
348
+ "Using EBU R128 normalization with --batch. For true album normalization where "
349
+ "all tracks are shifted by the same amount, consider using --normalization-type rms "
350
+ "or --normalization-type peak instead. EBU normalization applies different processing "
351
+ "to each track based on its loudness characteristics, which may alter relative levels "
352
+ "slightly due to psychoacoustic adjustments."
353
+ )
354
+
355
+ # Warn if using dynamic EBU mode with batch
356
+ if self.dynamic and self.normalization_type == "ebu":
357
+ _logger.warning(
358
+ "ffmpeg uses dynamic EBU normalization. This may change relative "
359
+ "loudness within a file. Use linear mode for true album normalization, or "
360
+ "switch to --normalization-type peak or --normalization-type rms instead. "
361
+ "To force linear mode, use --keep-lra-above-loudness-range-target or "
362
+ "--keep-loudness-range-target."
363
+ )
364
+
365
+ # Phase 1: Run first pass on all files to collect measurements
366
+ _logger.info("Phase 1: Analyzing all files...")
367
+ for index, media_file in enumerate(
368
+ tqdm(
369
+ self.media_files,
370
+ desc="Analysis",
371
+ disable=not self.progress,
372
+ position=0,
373
+ )
374
+ ):
375
+ _logger.info(
376
+ f"Analyzing file {media_file} ({index + 1} of {self.file_count})"
377
+ )
378
+
379
+ try:
380
+ # Only run first pass if not in dynamic EBU mode
381
+ if not (self.dynamic and self.normalization_type == "ebu"):
382
+ media_file._first_pass()
383
+ else:
384
+ _logger.debug(
385
+ "Dynamic EBU mode: First pass skipped for this file."
386
+ )
387
+ except Exception as e:
388
+ if len(self.media_files) > 1:
389
+ _logger.error(
390
+ f"Error analyzing input file {media_file}, will "
391
+ f"continue batch-processing. Error was: {e}"
392
+ )
393
+ else:
394
+ raise e
395
+
396
+ # Phase 2: Calculate batch reference loudness
397
+ batch_reference = self._calculate_batch_reference()
398
+
399
+ # Phase 3: Run second pass on all files with batch reference
400
+ _logger.info("Phase 2: Normalizing all files...")
401
+ for index, media_file in enumerate(
402
+ tqdm(
403
+ self.media_files,
404
+ desc="Normalization",
405
+ disable=not self.progress,
406
+ position=0,
407
+ )
408
+ ):
409
+ _logger.info(
410
+ f"Normalizing file {media_file} ({index + 1} of {self.file_count})"
411
+ )
412
+
413
+ try:
414
+ media_file.run_normalization(batch_reference=batch_reference)
415
+ except Exception as e:
416
+ if len(self.media_files) > 1:
417
+ _logger.error(
418
+ f"Error processing input file {media_file}, will "
419
+ f"continue batch-processing. Error was: {e}"
420
+ )
421
+ else:
422
+ raise e
423
+ else:
424
+ # Non-batch mode: process each file completely before moving to the next
425
+ for index, media_file in enumerate(
426
+ tqdm(
427
+ self.media_files, desc="File", disable=not self.progress, position=0
428
+ )
429
+ ):
430
+ _logger.info(
431
+ f"Normalizing file {media_file} ({index + 1} of {self.file_count})"
432
+ )
433
+
434
+ try:
435
+ media_file.run_normalization()
436
+ except Exception as e:
437
+ if len(self.media_files) > 1:
438
+ _logger.error(
439
+ f"Error processing input file {media_file}, will "
440
+ f"continue batch-processing. Error was: {e}"
441
+ )
442
+ else:
443
+ raise e
298
444
 
299
445
  if self.print_stats:
300
446
  json.dump(
@@ -86,6 +86,7 @@ class MediaFile:
86
86
  self.output_ext = current_ext
87
87
  self.streams: StreamDict = {"audio": {}, "video": {}, "subtitle": {}}
88
88
  self.temp_file: Union[str, None] = None
89
+ self.batch_reference: float | None = None
89
90
 
90
91
  self.parse_streams()
91
92
 
@@ -265,21 +266,35 @@ class MediaFile:
265
266
  # Normalize all streams (default behavior)
266
267
  return all_audio_streams
267
268
 
268
- def run_normalization(self) -> None:
269
+ def run_normalization(self, batch_reference: float | None = None) -> None:
269
270
  """
270
271
  Run the normalization process for this file.
272
+
273
+ Args:
274
+ batch_reference (float | None, optional): Reference loudness for batch mode.
275
+ If provided, the first pass is skipped (assumed already done) and this
276
+ reference is used to calculate relative adjustments. Defaults to None.
271
277
  """
272
278
  _logger.debug(f"Running normalization for {self.input_file}")
273
279
 
274
- # run the first pass to get loudness stats, unless in dynamic EBU mode
275
- if not (
276
- self.ffmpeg_normalize.dynamic
277
- and self.ffmpeg_normalize.normalization_type == "ebu"
278
- ):
279
- self._first_pass()
280
+ # Store batch reference for use in second pass
281
+ self.batch_reference = batch_reference
282
+
283
+ # run the first pass to get loudness stats, unless in dynamic EBU mode or batch mode
284
+ # (in batch mode, first pass is already done in FFmpegNormalize.run_normalization)
285
+ if batch_reference is None:
286
+ if not (
287
+ self.ffmpeg_normalize.dynamic
288
+ and self.ffmpeg_normalize.normalization_type == "ebu"
289
+ ):
290
+ self._first_pass()
291
+ else:
292
+ _logger.debug(
293
+ "Dynamic EBU mode: First pass will not run, as it is not needed."
294
+ )
280
295
  else:
281
296
  _logger.debug(
282
- "Dynamic EBU mode: First pass will not run, as it is not needed."
297
+ f"Batch mode: Skipping first pass (already completed), using batch reference = {batch_reference:.2f}"
283
298
  )
284
299
 
285
300
  # for second pass, create a temp file
@@ -529,9 +544,13 @@ class MediaFile:
529
544
  normalization_filter = "acopy"
530
545
  else:
531
546
  if self.ffmpeg_normalize.normalization_type == "ebu":
532
- normalization_filter = audio_stream.get_second_pass_opts_ebu()
547
+ normalization_filter = audio_stream.get_second_pass_opts_ebu(
548
+ batch_reference=self.batch_reference
549
+ )
533
550
  else:
534
- normalization_filter = audio_stream.get_second_pass_opts_peakrms()
551
+ normalization_filter = audio_stream.get_second_pass_opts_peakrms(
552
+ batch_reference=self.batch_reference
553
+ )
535
554
 
536
555
  input_label = f"[0:{audio_stream.stream_id}]"
537
556
  output_label = f"[norm{audio_stream.stream_id}]"
@@ -444,9 +444,14 @@ class AudioStream(MediaStream):
444
444
  )
445
445
  return result
446
446
 
447
- def get_second_pass_opts_ebu(self) -> str:
447
+ def get_second_pass_opts_ebu(self, batch_reference: float | None = None) -> str:
448
448
  """
449
449
  Return second pass loudnorm filter options string for ffmpeg
450
+
451
+ Args:
452
+ batch_reference (float | None, optional): Reference loudness for batch mode.
453
+ When provided, the target level is adjusted to preserve relative loudness.
454
+ Defaults to None.
450
455
  """
451
456
 
452
457
  # In dynamic mode, we can do everything in one pass, and we do not have first pass stats
@@ -552,6 +557,19 @@ class AudioStream(MediaStream):
552
557
 
553
558
  stats = self.loudness_statistics["ebu_pass1"]
554
559
 
560
+ # Adjust target level for batch mode to preserve relative loudness
561
+ if batch_reference is not None:
562
+ input_i = float(stats["input_i"])
563
+ # Formula: adjusted_target = target_level + (input_i - batch_reference)
564
+ # If track is quieter than average (input_i < batch_ref), offset is negative → quieter target
565
+ # If track is louder than average (input_i > batch_ref), offset is positive → louder target
566
+ adjusted_target = target_level + (input_i - batch_reference)
567
+ _logger.info(
568
+ f"Batch mode: Adjusting target from {target_level:.2f} to {adjusted_target:.2f} LUFS "
569
+ f"(input_i={input_i:.2f}, batch_ref={batch_reference:.2f}, offset={input_i - batch_reference:.2f})"
570
+ )
571
+ target_level = adjusted_target
572
+
555
573
  opts = {
556
574
  "i": target_level,
557
575
  "lra": self.media_file.ffmpeg_normalize.loudness_range_target,
@@ -576,11 +594,16 @@ class AudioStream(MediaStream):
576
594
 
577
595
  return "loudnorm=" + dict_to_filter_opts(opts)
578
596
 
579
- def get_second_pass_opts_peakrms(self) -> str:
597
+ def get_second_pass_opts_peakrms(self, batch_reference: float | None = None) -> str:
580
598
  """
581
599
  Set the adjustment gain based on chosen option and mean/max volume,
582
600
  return the matching ffmpeg volume filter.
583
601
 
602
+ Args:
603
+ batch_reference (float | None, optional): Reference loudness for batch mode.
604
+ When provided, the target level is adjusted to preserve relative loudness.
605
+ Defaults to None.
606
+
584
607
  Returns:
585
608
  str: ffmpeg volume filter string
586
609
  """
@@ -595,6 +618,27 @@ class AudioStream(MediaStream):
595
618
  normalization_type = self.media_file.ffmpeg_normalize.normalization_type
596
619
  target_level = self.media_file.ffmpeg_normalize.target_level
597
620
 
621
+ # Adjust target level for batch mode to preserve relative loudness
622
+ if batch_reference is not None:
623
+ if normalization_type == "peak":
624
+ measured_level = float(self.loudness_statistics["max"])
625
+ elif normalization_type == "rms":
626
+ measured_level = float(self.loudness_statistics["mean"])
627
+ else:
628
+ raise FFmpegNormalizeError(
629
+ "Can only set adjustment for peak and RMS normalization"
630
+ )
631
+
632
+ # Formula: adjusted_target = target_level + (measured_level - batch_reference)
633
+ # If track is quieter than average (measured < batch_ref), offset is negative → quieter target
634
+ # If track is louder than average (measured > batch_ref), offset is positive → louder target
635
+ adjusted_target = target_level + (measured_level - batch_reference)
636
+ _logger.info(
637
+ f"Batch mode: Adjusting target from {target_level:.2f} to {adjusted_target:.2f} dB "
638
+ f"(measured={measured_level:.2f}, batch_ref={batch_reference:.2f}, offset={measured_level - batch_reference:.2f})"
639
+ )
640
+ target_level = adjusted_target
641
+
598
642
  if normalization_type == "peak":
599
643
  adjustment = 0 + target_level - self.loudness_statistics["max"]
600
644
  elif normalization_type == "rms":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ffmpeg-normalize
3
- Version: 1.34.0
3
+ Version: 1.35.0
4
4
  Summary: Normalize audio via ffmpeg
5
5
  Keywords: ffmpeg,normalize,audio
6
6
  Author: Werner Robitza
@@ -46,12 +46,48 @@ This program normalizes media files to a certain loudness level using the EBU R1
46
46
 
47
47
  ## ✨ Features
48
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
49
+ - EBU R128 loudness normalization — Two-pass by default, with an option for one-pass dynamic normalization
50
+ - RMS-based normalization — Adjust audio to a specific RMS level
51
+ - Peak normalization — Adjust audio to a specific peak level
52
+ - Selective audio stream normalization — Normalize specific audio streams or only default streams
53
+ - Video file support — Process video files while preserving video streams
54
+ - Docker support — Run via Docker container
55
+ - Python API — Use programmatically in your Python projects
56
+ - Shell completions — Available for bash, zsh, and fish
57
+ - Album Batch normalization – Process files jointy, preserving relative loudness
58
+
59
+ ## 🆕 What's New
60
+
61
+ - Version 1.35.0 has **batch/album normalization** with `--batch`. It preserves relative loudness between files! Perfect for music albums where you want to shift all tracks by the same amount.
62
+
63
+ Example:
64
+
65
+ ```bash
66
+ ffmpeg-normalize album/*.flac --batch -nt rms -t -20
67
+ ```
68
+
69
+ shifts the entire album so the average RMS is -20 dB, preserving the original relative loudness as mastered.
70
+
71
+ - Version 1.34.0 brings **selective audio stream normalization**! You can now:
72
+
73
+ - Normalize specific audio streams with `-as/--audio-streams` (e.g., `-as 1,2` to normalize only streams 1 and 2)
74
+ - Normalize only default audio streams with `--audio-default-only` (useful for files with multiple language tracks)
75
+ - Keep other streams unchanged with `--keep-other-audio` (copy non-selected streams without normalization)
76
+
77
+ Example:
78
+
79
+ ```bash
80
+ ffmpeg-normalize input.mkv -as 1 --keep-other-audio
81
+ ```
82
+
83
+ normalizes stream 1 and copies all other audio streams unchanged.
84
+
85
+ Other recent additions:
86
+
87
+ - **Shell completions** (v1.31.0) — Tab completion for bash, zsh, and fish shells. See the [installation guide](https://slhck.info/ffmpeg-normalize/getting-started/installation/#shell-completions) for setup instructions.
88
+ - **`--lower-only` option** — Prevent audio from increasing in loudness, only lower it if needed (works with all normalization types).
89
+
90
+ See the [full changelog](https://github.com/slhck/ffmpeg-normalize/blob/master/CHANGELOG.md) for all updates.
55
91
 
56
92
  ## 🚀 Quick Start
57
93
 
@@ -0,0 +1,14 @@
1
+ ffmpeg_normalize/__init__.py,sha256=l0arjiMMBNbiH3IH67gT6SdZjPGAVLAdorUx38dNtvE,508
2
+ ffmpeg_normalize/__main__.py,sha256=ooM9MZRI4BfyF2G8eioY5KcumZ3jELRBkGNNchmF_ck,23788
3
+ ffmpeg_normalize/_cmd_utils.py,sha256=1JspVpguAPsq7DqvyvjUNzHhVv8J3X93xNOMwito_jY,5284
4
+ ffmpeg_normalize/_errors.py,sha256=brTQ4osJ4fTA8wnyMPVVYfGwJ0wqeShRFydTEwi_VEY,48
5
+ ffmpeg_normalize/_ffmpeg_normalize.py,sha256=NvFhiiYHLJz8M78WGEIit4Qbe-XsjLlrXvzKuNMW7v8,19973
6
+ ffmpeg_normalize/_logger.py,sha256=3Ap4Fxg7xGrzz7h4IGuNEf0KKstx0Rq_eLbHPrHzcrI,1841
7
+ ffmpeg_normalize/_media_file.py,sha256=awznS5C8ph6Mjy5dzwT-ubBpB3MXwbO94QFcn7mBejY,32197
8
+ ffmpeg_normalize/_streams.py,sha256=4Dnzuunhqz2qsOhlDv0dKML-lLjmPmUmM7M4dpn66Ow,24910
9
+ ffmpeg_normalize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ ffmpeg_normalize-1.35.0.dist-info/licenses/LICENSE.md,sha256=ig-_YggmJGbPQC_gUgBNFa0_XMsuHTpocivFnlOF4tE,1082
11
+ ffmpeg_normalize-1.35.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
12
+ ffmpeg_normalize-1.35.0.dist-info/entry_points.txt,sha256=1bdrW7-kJRc8tctjnGcfe_Fwx39z5JOm0yZnJHnmwl8,69
13
+ ffmpeg_normalize-1.35.0.dist-info/METADATA,sha256=Z4VJQhRECSX51YKaJFQkgY5CqaPxlgd6GIlyNYRFmSU,13387
14
+ ffmpeg_normalize-1.35.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- ffmpeg_normalize/__init__.py,sha256=l0arjiMMBNbiH3IH67gT6SdZjPGAVLAdorUx38dNtvE,508
2
- ffmpeg_normalize/__main__.py,sha256=pn5OePgr7-P5ajO3HxTMe4yQ1NR7wru5mGMHDklkbQI,22901
3
- ffmpeg_normalize/_cmd_utils.py,sha256=1JspVpguAPsq7DqvyvjUNzHhVv8J3X93xNOMwito_jY,5284
4
- ffmpeg_normalize/_errors.py,sha256=brTQ4osJ4fTA8wnyMPVVYfGwJ0wqeShRFydTEwi_VEY,48
5
- ffmpeg_normalize/_ffmpeg_normalize.py,sha256=ThIglofVOXxPMZcIrrYQ03HchlIOKmVSn41qILQNgg4,13193
6
- ffmpeg_normalize/_logger.py,sha256=3Ap4Fxg7xGrzz7h4IGuNEf0KKstx0Rq_eLbHPrHzcrI,1841
7
- ffmpeg_normalize/_media_file.py,sha256=Dz5vBDOelD1-GnlKX6830UbnlwvARFo3O_O-8Zyqlmw,31214
8
- ffmpeg_normalize/_streams.py,sha256=V5MnTjSnvQa6BNPSoFrUu0zg6mM-b9qaZE0ltGS2FV0,22329
9
- ffmpeg_normalize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- ffmpeg_normalize-1.34.0.dist-info/licenses/LICENSE.md,sha256=ig-_YggmJGbPQC_gUgBNFa0_XMsuHTpocivFnlOF4tE,1082
11
- ffmpeg_normalize-1.34.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
12
- ffmpeg_normalize-1.34.0.dist-info/entry_points.txt,sha256=1bdrW7-kJRc8tctjnGcfe_Fwx39z5JOm0yZnJHnmwl8,69
13
- ffmpeg_normalize-1.34.0.dist-info/METADATA,sha256=yoBY7Ig4p2ijd91p1NcT96URVZKZPaDWb0A0XyysIQY,11425
14
- ffmpeg_normalize-1.34.0.dist-info/RECORD,,