ffmpeg-normalize 1.27.6__py2.py3-none-any.whl → 1.28.0__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.
@@ -24,9 +24,7 @@ def create_parser() -> argparse.ArgumentParser:
24
24
  description=textwrap.dedent(
25
25
  """\
26
26
  ffmpeg-normalize v{} -- command line tool for normalizing audio files
27
- """.format(
28
- __version__
29
- )
27
+ """.format(__version__)
30
28
  ),
31
29
  # usage="%(prog)s INPUT [INPUT ...] [-o OUTPUT [OUTPUT ...]] [options]",
32
30
  formatter_class=argparse.RawTextHelpFormatter,
@@ -157,7 +155,7 @@ def create_parser() -> argparse.ArgumentParser:
157
155
  "-p",
158
156
  "--print-stats",
159
157
  action="store_true",
160
- help="Print first pass loudness statistics formatted as JSON to stdout",
158
+ help="Print loudness statistics for both passes formatted as JSON to stdout.",
161
159
  )
162
160
 
163
161
  # group_normalization.add_argument(
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
  import os
5
5
  import re
6
+ import shlex
6
7
  import subprocess
7
8
  from platform import system
8
9
  from shutil import which
@@ -75,7 +76,7 @@ class CommandRunner:
75
76
  float: Progress percentage
76
77
  """
77
78
  # wrapper for 'ffmpeg-progress-yield'
78
- _logger.debug(f"Running command: {cmd}")
79
+ _logger.debug(f"Running command: {shlex.join(cmd)}")
79
80
  ff = FfmpegProgress(cmd, dry_run=self.dry)
80
81
  yield from ff.run_command_with_progress()
81
82
 
@@ -96,7 +97,7 @@ class CommandRunner:
96
97
  Raises:
97
98
  RuntimeError: If command returns non-zero exit code
98
99
  """
99
- _logger.debug(f"Running command: {cmd}")
100
+ _logger.debug(f"Running command: {shlex.join(cmd)}")
100
101
 
101
102
  if self.dry:
102
103
  _logger.debug("Dry mode specified, not actually running command")
@@ -116,7 +117,7 @@ class CommandRunner:
116
117
  stderr = stderr_bytes.decode("utf8", errors="replace")
117
118
 
118
119
  if p.returncode != 0:
119
- raise RuntimeError(f"Error running command {cmd}: {stderr}")
120
+ raise RuntimeError(f"Error running command {shlex.join(cmd)}: {stderr}")
120
121
 
121
122
  self.output = stdout + stderr
122
123
  return self
@@ -19,7 +19,9 @@ if TYPE_CHECKING:
19
19
 
20
20
  _logger = logging.getLogger(__name__)
21
21
 
22
- AUDIO_ONLY_FORMATS = {"aac", "ast", "flac", "mp3", "mka", "oga", "ogg", "opus", "wav"}
22
+ # Note: this does not contain MP3, see https://github.com/slhck/ffmpeg-normalize/issues/246
23
+ # We may need to remove other formats as well, to be checked.
24
+ AUDIO_ONLY_FORMATS = {"aac", "ast", "flac", "mka", "oga", "ogg", "opus", "wav"}
23
25
  ONE_STREAM = {"aac", "ast", "flac", "mp3", "wav"}
24
26
 
25
27
 
@@ -230,12 +232,10 @@ class MediaFile:
230
232
  for _ in fun():
231
233
  pass
232
234
 
233
- if self.ffmpeg_normalize.print_stats:
234
- stats = [
235
- audio_stream.get_stats()
236
- for audio_stream in self.streams["audio"].values()
237
- ]
238
- self.ffmpeg_normalize.stats.extend(stats)
235
+ # set initial stats (for dry-runs, this is the only thing we need to do)
236
+ self.ffmpeg_normalize.stats = [
237
+ audio_stream.get_stats() for audio_stream in self.streams["audio"].values()
238
+ ]
239
239
 
240
240
  def _get_audio_filter_cmd(self) -> tuple[str, list[str]]:
241
241
  """
@@ -388,12 +388,14 @@ class MediaFile:
388
388
  temp_file = os.path.join(temp_dir, f"out.{self.output_ext}")
389
389
  cmd.append(temp_file)
390
390
 
391
+ cmd_runner = CommandRunner()
391
392
  try:
392
393
  try:
393
- yield from CommandRunner().run_ffmpeg_command(cmd)
394
+ yield from cmd_runner.run_ffmpeg_command(cmd)
394
395
  except Exception as e:
395
- cmd_str = " ".join([shlex.quote(c) for c in cmd])
396
- _logger.error(f"Error while running command {cmd_str}! Error: {e}")
396
+ _logger.error(
397
+ f"Error while running command {shlex.join(cmd)}! Error: {e}"
398
+ )
397
399
  raise e
398
400
  else:
399
401
  _logger.debug(
@@ -405,4 +407,32 @@ class MediaFile:
405
407
  rmtree(temp_dir, ignore_errors=True)
406
408
  raise e
407
409
 
410
+ output = cmd_runner.get_output()
411
+ # in the second pass, we do not normalize stream-by-stream, so we set the stats based on the
412
+ # overall output (which includes multiple loudnorm stats)
413
+ if self.ffmpeg_normalize.normalization_type == "ebu":
414
+ all_stats = AudioStream.prune_and_parse_loudnorm_output(
415
+ output, num_stats=len(self.streams["audio"])
416
+ )
417
+ for idx, audio_stream in self.streams["audio"].items():
418
+ audio_stream.set_second_pass_stats(all_stats[idx])
419
+
420
+ # collect all stats for the final report, again (overwrite the input)
421
+ self.ffmpeg_normalize.stats = [
422
+ audio_stream.get_stats() for audio_stream in self.streams["audio"].values()
423
+ ]
424
+
425
+ # warn if self.media_file.ffmpeg_normalize.dynamic == False and any of the second pass stats contain "normalization_type" == "dynamic"
426
+ if self.ffmpeg_normalize.dynamic is False:
427
+ for audio_stream in self.streams["audio"].values():
428
+ pass2_stats = audio_stream.get_stats()["ebu_pass2"]
429
+ if pass2_stats is None:
430
+ continue
431
+ if pass2_stats["normalization_type"] == "dynamic":
432
+ _logger.warning(
433
+ "You specified linear normalization, but the loudnorm filter reverted to dynamic normalization. "
434
+ "This may lead to unexpected results."
435
+ "Consider your input settings, e.g. choose a lower target level or higher target loudness range."
436
+ )
437
+
408
438
  _logger.debug("Normalization finished")
@@ -4,7 +4,7 @@ import json
4
4
  import logging
5
5
  import os
6
6
  import re
7
- from typing import TYPE_CHECKING, Iterator, Literal, TypedDict, cast
7
+ from typing import TYPE_CHECKING, Iterator, List, Literal, Optional, TypedDict, cast
8
8
 
9
9
  from ._cmd_utils import NUL, CommandRunner, dict_to_filter_opts
10
10
  from ._errors import FFmpegNormalizeError
@@ -26,10 +26,12 @@ class EbuLoudnessStatistics(TypedDict):
26
26
  output_lra: float
27
27
  output_thresh: float
28
28
  target_offset: float
29
+ normalization_type: str
29
30
 
30
31
 
31
32
  class LoudnessStatistics(TypedDict):
32
- ebu: EbuLoudnessStatistics | None
33
+ ebu_pass1: EbuLoudnessStatistics | None
34
+ ebu_pass2: EbuLoudnessStatistics | None
33
35
  mean: float | None
34
36
  max: float | None
35
37
 
@@ -107,7 +109,8 @@ class AudioStream(MediaStream):
107
109
  super().__init__(ffmpeg_normalize, media_file, "audio", stream_id)
108
110
 
109
111
  self.loudness_statistics: LoudnessStatistics = {
110
- "ebu": None,
112
+ "ebu_pass1": None,
113
+ "ebu_pass2": None,
111
114
  "mean": None,
112
115
  "max": None,
113
116
  }
@@ -156,12 +159,22 @@ class AudioStream(MediaStream):
156
159
  "input_file": self.media_file.input_file,
157
160
  "output_file": self.media_file.output_file,
158
161
  "stream_id": self.stream_id,
159
- "ebu": self.loudness_statistics["ebu"],
162
+ "ebu_pass1": self.loudness_statistics["ebu_pass1"],
163
+ "ebu_pass2": self.loudness_statistics["ebu_pass2"],
160
164
  "mean": self.loudness_statistics["mean"],
161
165
  "max": self.loudness_statistics["max"],
162
166
  }
163
167
  return stats
164
168
 
169
+ def set_second_pass_stats(self, stats: EbuLoudnessStatistics):
170
+ """
171
+ Set the EBU loudness statistics for the second pass.
172
+
173
+ Args:
174
+ stats (dict): The EBU loudness statistics.
175
+ """
176
+ self.loudness_statistics["ebu_pass2"] = stats
177
+
165
178
  def get_pcm_codec(self) -> str:
166
179
  """
167
180
  Get the PCM codec string for the stream.
@@ -288,6 +301,8 @@ class AudioStream(MediaStream):
288
301
  "-y",
289
302
  "-i",
290
303
  self.media_file.input_file,
304
+ "-map",
305
+ f"0:{self.stream_id}",
291
306
  "-filter_complex",
292
307
  filter_str,
293
308
  "-vn",
@@ -305,30 +320,69 @@ class AudioStream(MediaStream):
305
320
  f"Loudnorm first pass command output: {CommandRunner.prune_ffmpeg_progress_from_output(output)}"
306
321
  )
307
322
 
308
- output_lines = [line.strip() for line in output.split("\n")]
309
-
310
- self.loudness_statistics["ebu"] = AudioStream._parse_loudnorm_output(
311
- output_lines
323
+ self.loudness_statistics["ebu_pass1"] = (
324
+ AudioStream.prune_and_parse_loudnorm_output(
325
+ output, num_stats=1
326
+ )[0] # only one stream
312
327
  )
313
328
 
314
329
  @staticmethod
315
- def _parse_loudnorm_output(output_lines: list[str]) -> EbuLoudnessStatistics:
330
+ def prune_and_parse_loudnorm_output(
331
+ output: str, num_stats: int = 1
332
+ ) -> List[EbuLoudnessStatistics]:
333
+ """
334
+ Prune ffmpeg progress lines from output and parse the loudnorm filter output.
335
+ There may be multiple outputs if multiple streams were processed.
336
+
337
+ Args:
338
+ output (str): The output from ffmpeg.
339
+ num_stats (int): The number of loudnorm statistics to parse.
340
+
341
+ Returns:
342
+ list: The EBU loudness statistics.
343
+ """
344
+ pruned_output = CommandRunner.prune_ffmpeg_progress_from_output(output)
345
+ output_lines = [line.strip() for line in pruned_output.split("\n")]
346
+
347
+ ret = []
348
+ idx = 0
349
+ while True:
350
+ _logger.debug(f"Parsing loudnorm stats for stream {idx}")
351
+ loudnorm_stats = AudioStream._parse_loudnorm_output(
352
+ output_lines, stream_index=idx
353
+ )
354
+ idx += 1
355
+
356
+ if loudnorm_stats is None:
357
+ continue
358
+ ret.append(loudnorm_stats)
359
+
360
+ if len(ret) >= num_stats:
361
+ break
362
+
363
+ return ret
364
+
365
+ @staticmethod
366
+ def _parse_loudnorm_output(
367
+ output_lines: list[str], stream_index: Optional[int] = None
368
+ ) -> Optional[EbuLoudnessStatistics]:
316
369
  """
317
370
  Parse the output of a loudnorm filter to get the EBU loudness statistics.
318
371
 
319
372
  Args:
320
373
  output_lines (list[str]): The output lines of the loudnorm filter.
374
+ stream_index (int): The stream index, optional to filter out the correct stream. If unset, the first stream is used.
321
375
 
322
376
  Raises:
323
377
  FFmpegNormalizeError: When the output could not be parsed.
324
378
 
325
379
  Returns:
326
- EbuLoudnessStatistics: The EBU loudness statistics.
380
+ EbuLoudnessStatistics: The EBU loudness statistics, if found.
327
381
  """
328
382
  loudnorm_start = 0
329
383
  loudnorm_end = 0
330
384
  for index, line in enumerate(output_lines):
331
- if line.startswith("[Parsed_loudnorm"):
385
+ if line.startswith(f"[Parsed_loudnorm_{stream_index}"):
332
386
  loudnorm_start = index + 1
333
387
  continue
334
388
  if loudnorm_start and line.startswith("}"):
@@ -336,6 +390,10 @@ class AudioStream(MediaStream):
336
390
  break
337
391
 
338
392
  if not (loudnorm_start and loudnorm_end):
393
+ if stream_index is not None:
394
+ # not an error
395
+ return None
396
+
339
397
  raise FFmpegNormalizeError(
340
398
  "Could not parse loudnorm stats; no loudnorm-related output found"
341
399
  )
@@ -345,7 +403,9 @@ class AudioStream(MediaStream):
345
403
  "\n".join(output_lines[loudnorm_start:loudnorm_end])
346
404
  )
347
405
 
348
- _logger.debug(f"Loudnorm stats parsed: {json.dumps(loudnorm_stats)}")
406
+ _logger.debug(
407
+ f"Loudnorm stats for stream {stream_index} parsed: {json.dumps(loudnorm_stats)}"
408
+ )
349
409
 
350
410
  for key in [
351
411
  "input_i",
@@ -357,9 +417,14 @@ class AudioStream(MediaStream):
357
417
  "output_lra",
358
418
  "output_thresh",
359
419
  "target_offset",
420
+ "normalization_type",
360
421
  ]:
422
+ if key not in loudnorm_stats:
423
+ continue
424
+ if key == "normalization_type":
425
+ loudnorm_stats[key] = loudnorm_stats[key].lower()
361
426
  # handle infinite values
362
- if float(loudnorm_stats[key]) == -float("inf"):
427
+ elif float(loudnorm_stats[key]) == -float("inf"):
363
428
  loudnorm_stats[key] = -99
364
429
  elif float(loudnorm_stats[key]) == float("inf"):
365
430
  loudnorm_stats[key] = 0
@@ -378,17 +443,17 @@ class AudioStream(MediaStream):
378
443
  Return second pass loudnorm filter options string for ffmpeg
379
444
  """
380
445
 
381
- if not self.loudness_statistics["ebu"]:
446
+ if not self.loudness_statistics["ebu_pass1"]:
382
447
  raise FFmpegNormalizeError(
383
448
  "First pass not run, you must call parse_loudnorm_stats first"
384
449
  )
385
450
 
386
- if float(self.loudness_statistics["ebu"]["input_i"]) > 0:
451
+ if float(self.loudness_statistics["ebu_pass1"]["input_i"]) > 0:
387
452
  _logger.warning(
388
453
  "Input file had measured input loudness greater than zero "
389
- f"({self.loudness_statistics['ebu']['input_i']}), capping at 0"
454
+ f"({self.loudness_statistics['ebu_pass1']['input_i']}), capping at 0"
390
455
  )
391
- self.loudness_statistics["ebu"]["input_i"] = 0
456
+ self.loudness_statistics["ebu_pass1"]["input_i"] = 0
392
457
 
393
458
  will_use_dynamic_mode = self.media_file.ffmpeg_normalize.dynamic
394
459
 
@@ -396,7 +461,7 @@ class AudioStream(MediaStream):
396
461
  _logger.debug(
397
462
  "Keeping target loudness range in second pass loudnorm filter"
398
463
  )
399
- input_lra = self.loudness_statistics["ebu"]["input_lra"]
464
+ input_lra = self.loudness_statistics["ebu_pass1"]["input_lra"]
400
465
  if input_lra < 1 or input_lra > 50:
401
466
  _logger.warning(
402
467
  "Input file had measured loudness range outside of [1,50] "
@@ -404,12 +469,12 @@ class AudioStream(MediaStream):
404
469
  )
405
470
 
406
471
  self.media_file.ffmpeg_normalize.loudness_range_target = self._constrain(
407
- self.loudness_statistics["ebu"]["input_lra"], 1, 50
472
+ self.loudness_statistics["ebu_pass1"]["input_lra"], 1, 50
408
473
  )
409
474
 
410
475
  if self.media_file.ffmpeg_normalize.keep_lra_above_loudness_range_target:
411
476
  if (
412
- self.loudness_statistics["ebu"]["input_lra"]
477
+ self.loudness_statistics["ebu_pass1"]["input_lra"]
413
478
  <= self.media_file.ffmpeg_normalize.loudness_range_target
414
479
  ):
415
480
  _logger.debug(
@@ -417,7 +482,7 @@ class AudioStream(MediaStream):
417
482
  )
418
483
  else:
419
484
  self.media_file.ffmpeg_normalize.loudness_range_target = (
420
- self.loudness_statistics["ebu"]["input_lra"]
485
+ self.loudness_statistics["ebu_pass1"]["input_lra"]
421
486
  )
422
487
  _logger.debug(
423
488
  "Keeping target loudness range in second pass loudnorm filter"
@@ -425,11 +490,11 @@ class AudioStream(MediaStream):
425
490
 
426
491
  if (
427
492
  self.media_file.ffmpeg_normalize.loudness_range_target
428
- < self.loudness_statistics["ebu"]["input_lra"]
493
+ < self.loudness_statistics["ebu_pass1"]["input_lra"]
429
494
  and not will_use_dynamic_mode
430
495
  ):
431
496
  _logger.warning(
432
- f"Input file had loudness range of {self.loudness_statistics['ebu']['input_lra']}. "
497
+ f"Input file had loudness range of {self.loudness_statistics['ebu_pass1']['input_lra']}. "
433
498
  f"This is larger than the loudness range target ({self.media_file.ffmpeg_normalize.loudness_range_target}). "
434
499
  "Normalization will revert to dynamic mode. Choose a higher target loudness range if you want linear normalization. "
435
500
  "Alternatively, use the --keep-loudness-range-target or --keep-lra-above-loudness-range-target option to keep the target loudness range from "
@@ -443,7 +508,7 @@ class AudioStream(MediaStream):
443
508
  "Specify -ar/--sample-rate to override it."
444
509
  )
445
510
 
446
- stats = self.loudness_statistics["ebu"]
511
+ stats = self.loudness_statistics["ebu_pass1"]
447
512
 
448
513
  opts = {
449
514
  "i": self.media_file.ffmpeg_normalize.target_level,
@@ -1 +1 @@
1
- __version__ = "1.27.6"
1
+ __version__ = "1.28.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ffmpeg-normalize
3
- Version: 1.27.6
3
+ Version: 1.28.0
4
4
  Summary: Normalize audio via ffmpeg
5
5
  Home-page: https://github.com/slhck/ffmpeg-normalize
6
6
  Author: Werner Robitza
@@ -57,6 +57,7 @@ Read on for more info.
57
57
  - [Requirements](#requirements)
58
58
  - [ffmpeg](#ffmpeg)
59
59
  - [Installation](#installation)
60
+ - [Docker Build](#docker-build)
60
61
  - [Usage](#usage)
61
62
  - [Description](#description)
62
63
  - [Examples](#examples)
@@ -266,7 +267,7 @@ Some containers (like MP4) also cannot handle PCM audio. If you want to use such
266
267
 
267
268
  Otherwise, the range is -99 to 0.
268
269
 
269
- - `-p, --print-stats`: Print first pass loudness statistics formatted as JSON to stdout.
270
+ - `-p, --print-stats`: Print loudness statistics for both passes formatted as JSON to stdout.
270
271
 
271
272
  ### EBU R128 Normalization
272
273
 
@@ -562,6 +563,18 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
562
563
  # Changelog
563
564
 
564
565
 
566
+ ## v1.28.0 (2024-05-13)
567
+
568
+ * Warn if dynamic mode is used but linear specified (#256)
569
+
570
+ * Print debug commands in shell-escaped form.
571
+
572
+
573
+ ## v1.27.7 (2023-09-26)
574
+
575
+ * Allow cover art in MP3.
576
+
577
+
565
578
  ## v1.27.6 (2023-07-22)
566
579
 
567
580
  * Remove warning for short files (#87)
@@ -0,0 +1,16 @@
1
+ ffmpeg_normalize/__init__.py,sha256=aAhlk93ZE6SQcWUDzZQcw9vJh0bJcKEUNFGhVc5ZIto,453
2
+ ffmpeg_normalize/__main__.py,sha256=Y1iJutUrHyvr6HTM9Hk7r1PEvkPBbaP99kTQRpNVUbw,18104
3
+ ffmpeg_normalize/_cmd_utils.py,sha256=A6quxjOl5tLHxgcs7SdMXzuOCP_JjUTzL1iUD4G7DJk,5361
4
+ ffmpeg_normalize/_errors.py,sha256=brTQ4osJ4fTA8wnyMPVVYfGwJ0wqeShRFydTEwi_VEY,48
5
+ ffmpeg_normalize/_ffmpeg_normalize.py,sha256=zvqxGvyAfx1pZr7kSlhHACx5qJC-atlmQpbVIptVHUQ,10346
6
+ ffmpeg_normalize/_logger.py,sha256=3Ap4Fxg7xGrzz7h4IGuNEf0KKstx0Rq_eLbHPrHzcrI,1841
7
+ ffmpeg_normalize/_media_file.py,sha256=o6N_0be1Z4KsuWz5o6IP3gXix4Pqrhvy_roxo06NMic,16298
8
+ ffmpeg_normalize/_streams.py,sha256=5zLlSK2vMTBs0HzBTmI5UiJVqSp1Nk19TZXpxNtjkIA,19973
9
+ ffmpeg_normalize/_version.py,sha256=4uJZcB2NwmcUxmnUm9N9cn2pOxmFcTiUDKF7ilQLWck,23
10
+ ffmpeg_normalize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ ffmpeg_normalize-1.28.0.dist-info/LICENSE,sha256=zeqAHGWrSIwdPHsZMZv1_N0gGFO1xxjcZEz9CplR4EM,1086
12
+ ffmpeg_normalize-1.28.0.dist-info/METADATA,sha256=iqT0DqRw00W1K8MkeeVSihw2D3bQ_qz-Gc-kvTwCwJk,50864
13
+ ffmpeg_normalize-1.28.0.dist-info/WHEEL,sha256=-G_t0oGuE7UD0DrSpVZnq1hHMBV9DD2XkS5v7XpmTnk,110
14
+ ffmpeg_normalize-1.28.0.dist-info/entry_points.txt,sha256=X0EC5ptb0iGOxrk3Aa65dVQtvUixngLd_2-iAtSixdc,68
15
+ ffmpeg_normalize-1.28.0.dist-info/top_level.txt,sha256=wnUkr17ckPrrU1JsxZQiXbEBUnHKsC64yck-MemEBuI,17
16
+ ffmpeg_normalize-1.28.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.38.4)
2
+ Generator: bdist_wheel (0.42.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=j0cbuTAbjy7B-QXmNVUIR-R6AxhyM6uoACvYy01cwxQ,18128
3
- ffmpeg_normalize/_cmd_utils.py,sha256=-jANfl9kM-VdLHXeoCeipJ4YkiF59NUtzu_bj6VGUAw,5312
4
- ffmpeg_normalize/_errors.py,sha256=brTQ4osJ4fTA8wnyMPVVYfGwJ0wqeShRFydTEwi_VEY,48
5
- ffmpeg_normalize/_ffmpeg_normalize.py,sha256=zvqxGvyAfx1pZr7kSlhHACx5qJC-atlmQpbVIptVHUQ,10346
6
- ffmpeg_normalize/_logger.py,sha256=3Ap4Fxg7xGrzz7h4IGuNEf0KKstx0Rq_eLbHPrHzcrI,1841
7
- ffmpeg_normalize/_media_file.py,sha256=3QjU2IqXquze9Bwwi-edcgAh74SyIxBAQvnM-NrXLXM,14568
8
- ffmpeg_normalize/_streams.py,sha256=gxyS1D6q1f1mfPZY1vQHnh0HiVNXuFZarh1cBaXobEE,17620
9
- ffmpeg_normalize/_version.py,sha256=gWl_EviZqHKCyKsjZfujTw9QCWXDNbvNLyjrZBqXX-w,23
10
- ffmpeg_normalize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- ffmpeg_normalize-1.27.6.dist-info/LICENSE,sha256=zeqAHGWrSIwdPHsZMZv1_N0gGFO1xxjcZEz9CplR4EM,1086
12
- ffmpeg_normalize-1.27.6.dist-info/METADATA,sha256=2xKWmPMIaggXwhDidnVuKJNjNNmLniPblYoph9tMs0k,50641
13
- ffmpeg_normalize-1.27.6.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
14
- ffmpeg_normalize-1.27.6.dist-info/entry_points.txt,sha256=X0EC5ptb0iGOxrk3Aa65dVQtvUixngLd_2-iAtSixdc,68
15
- ffmpeg_normalize-1.27.6.dist-info/top_level.txt,sha256=wnUkr17ckPrrU1JsxZQiXbEBUnHKsC64yck-MemEBuI,17
16
- ffmpeg_normalize-1.27.6.dist-info/RECORD,,