audiolibrarian 0.16.5__py3-none-any.whl → 0.17.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.
@@ -1,7 +1,7 @@
1
1
  """The audiolibrarian package."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -16,4 +16,4 @@
16
16
  # You should have received a copy of the GNU General Public License along with audiolibrarian.
17
17
  # If not, see <https://www.gnu.org/licenses/>.
18
18
  #
19
- __version__ = "0.16.5"
19
+ __version__ = "0.17.0"
@@ -1,7 +1,6 @@
1
1
  """Audio file library."""
2
-
3
2
  #
4
- # Copyright (c) 2020 Stephen Jibson
3
+ # Copyright (c) 2000-2025 Stephen Jibson
5
4
  #
6
5
  # This file is part of audiolibrarian.
7
6
  #
@@ -1,7 +1,7 @@
1
1
  """Audio file library."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1 +1,17 @@
1
1
  """AudioFile formats."""
2
+ #
3
+ # Copyright (c) 2000-2025 Stephen Jibson
4
+ #
5
+ # This file is part of audiolibrarian.
6
+ #
7
+ # Audiolibrarian is free software: you can redistribute it and/or modify it under the terms of the
8
+ # GNU General Public License as published by the Free Software Foundation, either version 3 of the
9
+ # License, or (at your option) any later version.
10
+ #
11
+ # Audiolibrarian is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12
+ # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
13
+ # the GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License along with audiolibrarian.
16
+ # If not, see <https://www.gnu.org/licenses/>.
17
+ #
@@ -1,7 +1,7 @@
1
1
  """AudioFile support for flac files."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  """AudioFile support for m4a files."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  """AudioFile support for mp3 files."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  """Manage tags."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  """AudioSource."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
audiolibrarian/base.py CHANGED
@@ -4,7 +4,7 @@ Useful stuff: https://help.mp3tag.de/main_tags.html
4
4
  """
5
5
 
6
6
  #
7
- # Copyright (c) 2020 Stephen Jibson
7
+ # Copyright (c) 2000-2025 Stephen Jibson
8
8
  #
9
9
  # This file is part of audiolibrarian.
10
10
  #
@@ -27,9 +27,10 @@ import subprocess
27
27
  import sys
28
28
  import warnings
29
29
  from collections.abc import Iterable
30
- from typing import Any
30
+ from typing import Any, Final
31
31
 
32
32
  import colors
33
+ import ffmpeg_normalize
33
34
  import filelock
34
35
  import yaml
35
36
 
@@ -46,6 +47,7 @@ class Base:
46
47
  """
47
48
 
48
49
  command: str | None = None
50
+ _manifest_file: Final[str] = "Manifest.yaml"
49
51
 
50
52
  def __init__(self, args: argparse.Namespace) -> None:
51
53
  """Initialize the base."""
@@ -66,9 +68,10 @@ class Base:
66
68
  self._source_dir = self._work_dir / "source"
67
69
  self._wav_dir = self._work_dir / "wav"
68
70
 
69
- self._manifest_file = "Manifest.yaml"
70
71
  self._lock = filelock.FileLock(str(self._work_dir) + ".lock")
71
72
 
73
+ self._normalizer = self._which_normalizer()
74
+
72
75
  # Initialize stuff that will be defined later.
73
76
  self._audio_source: audiosource.AudioSource | None = None
74
77
  self._release: records.Release | None = None
@@ -126,24 +129,6 @@ class Base:
126
129
  self._make_mp3()
127
130
  self._move_files(move_source=make_source)
128
131
 
129
- @staticmethod
130
- def _find_audio_files(directories: list[str | pathlib.Path]) -> Iterable[audiofile.AudioFile]:
131
- """Yield audiofile objects found in the given directories."""
132
- paths: list[pathlib.Path] = []
133
- # Grab all the paths first because thing may change as files are renamed.
134
- for directory in directories:
135
- path = pathlib.Path(directory)
136
- for ext in audiofile.AudioFile.extensions():
137
- paths.extend(path.rglob(f"*{ext}"))
138
- paths = sorted(set(paths))
139
- # Using yield rather than returning a list saves us from simultaneously storing
140
- # potentially thousands of AudioFile objects in memory at the same time.
141
- for path in paths:
142
- try:
143
- yield audiofile.AudioFile.open(path)
144
- except FileNotFoundError:
145
- continue
146
-
147
132
  def _find_manifests(self, directories: list[str | pathlib.Path]) -> list[pathlib.Path]:
148
133
  """Return a sorted, unique list of manifest files anywhere in the given directories."""
149
134
  manifests = set()
@@ -283,25 +268,36 @@ class Base:
283
268
  path.rename(source_dir / path.name)
284
269
 
285
270
  def _normalize(self) -> None:
286
- """Normalize the wav files using wavegain."""
287
- print("Normalizing wav files...")
288
- command = [
289
- "wavegain",
290
- f"--{SETTINGS.normalize_preset}",
291
- f"--gain={SETTINGS.normalize_gain}",
292
- "--apply",
293
- ]
294
- command.extend(str(f) for f in self._wav_filenames)
295
- result = subprocess.run(command, capture_output=True, check=False) # noqa: S603
296
- for line in str(result.stderr).split(r"\n"):
297
- line_trunc = line[:137] + "..." if len(line) > 140 else line # noqa: PLR2004
298
- log.info("WAVEGAIN: %s", line_trunc)
299
- result.check_returncode()
271
+ """Normalize the wav files using the selected normalizer."""
272
+ if self._normalizer == "none":
273
+ return
274
+ print(f"Normalizing wav files using {self._normalizer}...")
275
+
276
+ if self._normalizer == "wavegain":
277
+ command = [
278
+ "wavegain",
279
+ f"--{SETTINGS.normalize.wavegain.preset}",
280
+ f"--gain={SETTINGS.normalize.wavegain.gain}",
281
+ "--apply",
282
+ ]
283
+ command.extend(str(f) for f in self._wav_filenames)
284
+ result = subprocess.run(command, capture_output=True, check=False) # noqa: S603
285
+ for line in str(result.stderr).split(r"\n"):
286
+ line_trunc = line[:137] + "..." if len(line) > 140 else line # noqa: PLR2004
287
+ log.info("WAVEGAIN: %s", line_trunc)
288
+ result.check_returncode()
289
+ return
300
290
 
301
- @staticmethod
302
- def _read_manifest(manifest_path: pathlib.Path) -> dict[Any, Any]:
303
- with manifest_path.open(encoding="utf-8") as manifest_file:
304
- return dict(yaml.safe_load(manifest_file))
291
+ normalizer = ffmpeg_normalize.FFmpegNormalize(
292
+ extension="wav",
293
+ keep_loudness_range_target=True,
294
+ target_level=SETTINGS.normalize.ffmpeg.target_level,
295
+ )
296
+ for wav_file in self._wav_filenames:
297
+ normalizer.add_media_file(str(wav_file), str(wav_file))
298
+ log.info("NORMALIZER: starting ffmpeg normalization...")
299
+ normalizer.run_normalization()
300
+ log.info("NORMALIZER: ffmpeg normalization completed successfully")
305
301
 
306
302
  def _rename_wav(self) -> None:
307
303
  """Rename the wav files to a filename-sane representation of the track title."""
@@ -431,3 +427,54 @@ class Base:
431
427
  with pathlib.Path(manifest_filename).open("w", encoding="utf-8") as manifest_file:
432
428
  yaml.dump(manifest, manifest_file)
433
429
  print(f"Wrote {manifest_filename}")
430
+
431
+ @staticmethod
432
+ def _which_normalizer() -> str:
433
+ """Determine which normalizer to use based on settings and availability.
434
+
435
+ Returns:
436
+ str: The name of the normalizer to use ("wavegain", "ffmpeg" or "none")
437
+ """
438
+ normalizer = SETTINGS.normalize.normalizer
439
+ if normalizer == "none":
440
+ return "none"
441
+
442
+ wavegain_found = shutil.which("wavegain")
443
+ if normalizer in ("auto", "wavegain") and wavegain_found:
444
+ return "wavegain"
445
+
446
+ ffmpeg_found = shutil.which("ffmpeg")
447
+ if normalizer in ("auto", "ffmpeg") and ffmpeg_found:
448
+ return "ffmpeg"
449
+
450
+ if wavegain_found:
451
+ log.warning("ffmpeg not found, using wavegain for normalization")
452
+ return "wavegain"
453
+ if ffmpeg_found:
454
+ log.warning("wavegain not found, using ffmpeg for normalization")
455
+ return "ffmpeg"
456
+ log.warning("wavegain not found, ffmpeg not found, using no normalization")
457
+ return "none"
458
+
459
+ @staticmethod
460
+ def _find_audio_files(directories: list[str | pathlib.Path]) -> Iterable[audiofile.AudioFile]:
461
+ """Yield audiofile objects found in the given directories."""
462
+ paths: list[pathlib.Path] = []
463
+ # Grab all the paths first because thing may change as files are renamed.
464
+ for directory in directories:
465
+ path = pathlib.Path(directory)
466
+ for ext in audiofile.AudioFile.extensions():
467
+ paths.extend(path.rglob(f"*{ext}"))
468
+ paths = sorted(set(paths))
469
+ # Using yield rather than returning a list saves us from simultaneously storing
470
+ # potentially thousands of AudioFile objects in memory at the same time.
471
+ for path in paths:
472
+ try:
473
+ yield audiofile.AudioFile.open(path)
474
+ except FileNotFoundError:
475
+ continue
476
+
477
+ @staticmethod
478
+ def _read_manifest(manifest_path: pathlib.Path) -> dict[Any, Any]:
479
+ with manifest_path.open(encoding="utf-8") as manifest_file:
480
+ return dict(yaml.safe_load(manifest_file))
audiolibrarian/cli.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Audiolibrarian command line interface."""
2
2
 
3
3
  #
4
- # Copyright (c) 2021 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -40,7 +40,6 @@ class CommandLineInterface:
40
40
  "lame",
41
41
  "mpg123",
42
42
  "sndfile-convert",
43
- "wavegain",
44
43
  }
45
44
 
46
45
  def __init__(self, *, parse_args: bool = True) -> None:
@@ -1,7 +1,7 @@
1
1
  """Command line commands."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  """Genre Manager."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1,7 +1,7 @@
1
1
  """Access the MusicBrainz service."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
audiolibrarian/output.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Screen output utilities."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
audiolibrarian/records.py CHANGED
@@ -4,7 +4,7 @@ Useful field reference: https://github.com/metabrainz/picard/blob/master/picard/
4
4
  """
5
5
 
6
6
  #
7
- # Copyright (c) 2020 Stephen Jibson
7
+ # Copyright (c) 2000-2025 Stephen Jibson
8
8
  #
9
9
  # This file is part of audiolibrarian.
10
10
  #
@@ -21,23 +21,34 @@ configuration and cache locations.
21
21
  Sensitive fields (like passwords) are handled using pydantic.SecretStr for security.
22
22
  """
23
23
 
24
+ #
25
+ # Copyright (c) 2000-2025 Stephen Jibson
26
+ #
27
+ # This file is part of audiolibrarian.
28
+ #
29
+ # Audiolibrarian is free software: you can redistribute it and/or modify it under the terms of the
30
+ # GNU General Public License as published by the Free Software Foundation, either version 3 of the
31
+ # License, or (at your option) any later version.
32
+ #
33
+ # Audiolibrarian is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
34
+ # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
35
+ # the GNU General Public License for more details.
36
+ #
37
+ # You should have received a copy of the GNU General Public License along with audiolibrarian.
38
+ # If not, see <https://www.gnu.org/licenses/>.
39
+ #
24
40
  import logging
25
41
  import pathlib
26
42
  from typing import Literal
27
43
 
28
44
  import pydantic
45
+ import pydantic_settings
29
46
  import xdg_base_dirs
30
- from pydantic_settings import (
31
- BaseSettings,
32
- PydanticBaseSettingsSource,
33
- SettingsConfigDict,
34
- YamlConfigSettingsSource,
35
- )
36
47
 
37
48
  logger = logging.getLogger(__name__)
38
49
 
39
50
 
40
- class MusicBrainzSettings(BaseSettings):
51
+ class MusicBrainzSettings(pydantic_settings.BaseSettings):
41
52
  """Configuration settings for MusicBrainz."""
42
53
 
43
54
  password: pydantic.SecretStr = pydantic.SecretStr("")
@@ -45,17 +56,37 @@ class MusicBrainzSettings(BaseSettings):
45
56
  rate_limit: pydantic.PositiveFloat = 1.5 # Seconds between requests.
46
57
 
47
58
 
48
- class Settings(BaseSettings):
59
+ class NormalizeFFmpegSettings(pydantic_settings.BaseSettings):
60
+ """Configuration settings for ffmpeg normalization."""
61
+
62
+ target_level: float = -13
63
+
64
+
65
+ class NormalizeWavegainSettings(pydantic_settings.BaseSettings):
66
+ """Configuration settings for wavegain normalization."""
67
+
68
+ gain: int = 5 # dB
69
+ preset: Literal["album", "radio"] = "radio"
70
+
71
+
72
+ class NormalizeSettings(pydantic_settings.BaseSettings):
73
+ """Configuration settings for audio normalization."""
74
+
75
+ normalizer: Literal["auto", "wavegain", "ffmpeg", "none"] = "auto"
76
+ ffmpeg: NormalizeFFmpegSettings = NormalizeFFmpegSettings()
77
+ wavegain: NormalizeWavegainSettings = NormalizeWavegainSettings()
78
+
79
+
80
+ class Settings(pydantic_settings.BaseSettings):
49
81
  """Configuration settings for AudioLibrarian."""
50
82
 
51
83
  discid_device: str | None = None # Use default device.
52
84
  library_dir: pathlib.Path = pathlib.Path("library").resolve()
53
85
  musicbrainz: MusicBrainzSettings = MusicBrainzSettings()
54
- normalize_gain: int = 5 # dB
55
- normalize_preset: Literal["album", "radio"] = "radio"
86
+ normalize: NormalizeSettings = NormalizeSettings()
56
87
  work_dir: pathlib.Path = xdg_base_dirs.xdg_cache_home() / "audiolibrarian"
57
88
 
58
- model_config = SettingsConfigDict(
89
+ model_config = pydantic_settings.SettingsConfigDict(
59
90
  env_nested_delimiter="__",
60
91
  env_prefix="AUDIOLIBRARIAN__",
61
92
  yaml_file=str(xdg_base_dirs.xdg_config_home() / "audiolibrarian" / "config.yaml"),
@@ -65,14 +96,18 @@ class Settings(BaseSettings):
65
96
  @classmethod
66
97
  def settings_customise_sources(
67
98
  cls,
68
- settings_cls: type[BaseSettings],
69
- init_settings: PydanticBaseSettingsSource,
70
- env_settings: PydanticBaseSettingsSource,
71
- dotenv_settings: PydanticBaseSettingsSource,
72
- file_secret_settings: PydanticBaseSettingsSource,
73
- ) -> tuple[PydanticBaseSettingsSource, ...]:
99
+ settings_cls: type[pydantic_settings.BaseSettings],
100
+ init_settings: pydantic_settings.PydanticBaseSettingsSource,
101
+ env_settings: pydantic_settings.PydanticBaseSettingsSource,
102
+ dotenv_settings: pydantic_settings.PydanticBaseSettingsSource,
103
+ file_secret_settings: pydantic_settings.PydanticBaseSettingsSource,
104
+ ) -> tuple[pydantic_settings.PydanticBaseSettingsSource, ...]:
74
105
  del dotenv_settings, file_secret_settings # Unused.
75
- return env_settings, YamlConfigSettingsSource(settings_cls), init_settings
106
+ return (
107
+ env_settings,
108
+ pydantic_settings.YamlConfigSettingsSource(settings_cls),
109
+ init_settings,
110
+ )
76
111
 
77
112
 
78
113
  SETTINGS = Settings()
audiolibrarian/sh.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Command execution helpers."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
audiolibrarian/text.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Text utilities."""
2
2
 
3
3
  #
4
- # Copyright (c) 2020 Stephen Jibson
4
+ # Copyright (c) 2000-2025 Stephen Jibson
5
5
  #
6
6
  # This file is part of audiolibrarian.
7
7
  #
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audiolibrarian
3
- Version: 0.16.5
3
+ Version: 0.17.0
4
4
  Summary: Manage my audio library.
5
5
  Project-URL: Repository, https://github.com/toadstule/audiolibrarian
6
6
  Author-email: Steve Jibson <steve@jibson.com>
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python
12
12
  Requires-Python: <3.14,>=3.12
13
13
  Requires-Dist: ansicolors
14
14
  Requires-Dist: discid
15
+ Requires-Dist: ffmpeg-normalize
15
16
  Requires-Dist: filelock
16
17
  Requires-Dist: fuzzywuzzy
17
18
  Requires-Dist: musicbrainzngs
@@ -0,0 +1,28 @@
1
+ audiolibrarian/__init__.py,sha256=nk7MHHw3booS4scifS56NhceSDNsLnU7vcljwNcLcZ0,792
2
+ audiolibrarian/audiosource.py,sha256=Xs8KBvb0Jd-JRHv3veyBA9lL2siC70-gO2RdZqy2ntk,8550
3
+ audiolibrarian/base.py,sha256=yDN2PELNboUr_eD-Ix8xi-JHhq8XbDuaj4AnvergSIE,21263
4
+ audiolibrarian/cli.py,sha256=vtAJND7U5yoBCbm20HRCvIFp2HYzXl3jfHNfsNUQVlE,4196
5
+ audiolibrarian/commands.py,sha256=rM44NQUKtZsBI3C8EY0u0vRrricfj3felM00x16fBeU,11171
6
+ audiolibrarian/genremanager.py,sha256=ag4LGcXPHjuI-Y07DTmGjVcaUKyKazyWHGBak9YzVAg,7362
7
+ audiolibrarian/musicbrainz.py,sha256=hoZKDueqgfMo-Da_ZH3_bQcRXqRvaz9KCx9MARMI5h8,19317
8
+ audiolibrarian/output.py,sha256=MQGsKrOfkRASGkVG4CEi2kgj0smEm8TfXqAR-lwULwk,1707
9
+ audiolibrarian/records.py,sha256=87DXbpwzl8Fsh71Nzi5XmJDAaEGvRjH6vwJ75cIM9PQ,7934
10
+ audiolibrarian/settings.py,sha256=GSuT3zOa6jyrejx14_dLwJaTA6eCTNYFq6EQ9UQOzhI,4042
11
+ audiolibrarian/sh.py,sha256=7V-FrSSiq56cNL1IyYBEcSUXtMbwFs6Ob1EhU_i_c84,1917
12
+ audiolibrarian/text.py,sha256=VrxrQm6pZtuYbK9MlukSC3MdqDBSlsnTgkhBTwj9MpY,4031
13
+ audiolibrarian/audiofile/__init__.py,sha256=PTBAVV5gfSc0UVvWMHyeL1d3xwhgJ8J00L-KVhuCGhY,898
14
+ audiolibrarian/audiofile/audiofile.py,sha256=UCVXu2ga7stnTzeeLzrx-JTFl5ad82qy45HmB3g2a8c,4357
15
+ audiolibrarian/audiofile/tags.py,sha256=Di8TjvOYZYYgedMNRZ8tTGFxZ1KN4oAK7z17TzTSeZg,1790
16
+ audiolibrarian/audiofile/formats/__init__.py,sha256=DexI0KdI6hnbDhvK9xuEFvYyyQA2SOFT_xfadc42Nsk,759
17
+ audiolibrarian/audiofile/formats/flac.py,sha256=UNXfZbnL0cjzmlS3nuZ26QczrfD-twgc7S6PbtoOkqA,9713
18
+ audiolibrarian/audiofile/formats/m4a.py,sha256=Ki-pHnObeEMXegIAHu7ymtB_PjMiWLDU5HHXaWxaPPo,10557
19
+ audiolibrarian/audiofile/formats/mp3.py,sha256=SQOcPVAE-coDz4tK-gopWSWJE02nQBTbxrlmRkyK7QU,12886
20
+ picard_src/README.md,sha256=mtJ7RNLlC7Oz9M038WU-3ciPa7jPdXlFLYdJBL8iRQo,411
21
+ picard_src/__init__.py,sha256=acu0-oac_qfEgiB0rw0RvuL---O15-rOckWboVMxWtM,198
22
+ picard_src/textencoding.py,sha256=0MRHFwhqEwauQbjTTz6gpgzo6YH1VDPfdJqQoCWHtjM,26234
23
+ audiolibrarian-0.17.0.dist-info/METADATA,sha256=CvHJpVXiHY01H6rQ828Nu_a4Yzr-KXrXWXh5BgOl-X0,2578
24
+ audiolibrarian-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ audiolibrarian-0.17.0.dist-info/entry_points.txt,sha256=reubnr_SGbTTDXji8j7z8aTmIL0AEQKVSLcnmFG3YYY,59
26
+ audiolibrarian-0.17.0.dist-info/licenses/COPYING,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
27
+ audiolibrarian-0.17.0.dist-info/licenses/LICENSE,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
28
+ audiolibrarian-0.17.0.dist-info/RECORD,,
@@ -1,28 +0,0 @@
1
- audiolibrarian/__init__.py,sha256=gY9TxON-FJ8rKpy6s2RKFMpH0qMDrgDtE9Nn6q8xIjM,787
2
- audiolibrarian/audiosource.py,sha256=hQVFc2VQfCPV1kQRGOwwNAuMx4AXno0zcf_0Ps_Y2ts,8545
3
- audiolibrarian/base.py,sha256=54s_NacDvyGpvc_uOJO5apmQin4HKUGfPrlw0XFiJoI,19428
4
- audiolibrarian/cli.py,sha256=azX49RO2t4q8qyUJ9WTOlDqUO13p17mq0wNmEdl6QGw,4211
5
- audiolibrarian/commands.py,sha256=4oyXDSvzlL2vb1SyD2GVy8bDD9MTJz9IuTMl2PMLOQ4,11166
6
- audiolibrarian/genremanager.py,sha256=omjsn-8Bdz3etLu-qNAXhPUOyY4Byy6muwvkHyPM6so,7357
7
- audiolibrarian/musicbrainz.py,sha256=GwTrwvn-hLZw4uLD7CshyegLCM1tfeNL00rqz2Xsl20,19312
8
- audiolibrarian/output.py,sha256=JHuttwEMEzUxty7P8e2jaL6702q52fTWAdZEgb85bxQ,1702
9
- audiolibrarian/records.py,sha256=BovHwNVd0nTTwQPYkhOAOIVASWz9l47j6cGdXAx5VjM,7929
10
- audiolibrarian/settings.py,sha256=qKQ9JYfKr2k_JceWfYPiMlpBKylujCQI_2pmxWpsGFo,2550
11
- audiolibrarian/sh.py,sha256=tVTuKXlMH738jTopjnMWqidTaH02_EyHau3q9kQ6mYE,1912
12
- audiolibrarian/text.py,sha256=05q_E22w9hMsy01_A5XlU2pqQIMlM5RF4DXzYuLCcYo,4026
13
- audiolibrarian/audiofile/__init__.py,sha256=BRjf4iu_cogB3A5oIVE4BjIgUGCH0dAzPG4A_48rOUM,894
14
- audiolibrarian/audiofile/audiofile.py,sha256=IhTIzFzYeKfJmsgOG7bN62KWD06ehOGRoVv3eCf-_vw,4352
15
- audiolibrarian/audiofile/tags.py,sha256=42oyqHZ6jRokmQskD2p53NMgKxJR7xkidXImOhUzszY,1785
16
- audiolibrarian/audiofile/formats/__init__.py,sha256=ZV5urTtQQ4ZX_K2s9qFsSzDunvdQaQ43K7CE-BPPtEo,25
17
- audiolibrarian/audiofile/formats/flac.py,sha256=3MfzdgnAqTVSbAraAVde2BSHwdAyi-fKkc55OvuRNSc,9708
18
- audiolibrarian/audiofile/formats/m4a.py,sha256=S0aFZxMzgwpzkwYE2D_K_US0Dnh0objK21ph1S_8rJk,10552
19
- audiolibrarian/audiofile/formats/mp3.py,sha256=ipuanK7CIYSk9LqW0AvAgKAUZeU9ecwK0uZf2AhAGXo,12881
20
- picard_src/README.md,sha256=mtJ7RNLlC7Oz9M038WU-3ciPa7jPdXlFLYdJBL8iRQo,411
21
- picard_src/__init__.py,sha256=acu0-oac_qfEgiB0rw0RvuL---O15-rOckWboVMxWtM,198
22
- picard_src/textencoding.py,sha256=0MRHFwhqEwauQbjTTz6gpgzo6YH1VDPfdJqQoCWHtjM,26234
23
- audiolibrarian-0.16.5.dist-info/METADATA,sha256=m92zy8tCb5anGh-_ltl2Hca0jkB8QRW6locAtVnuskQ,2546
24
- audiolibrarian-0.16.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- audiolibrarian-0.16.5.dist-info/entry_points.txt,sha256=reubnr_SGbTTDXji8j7z8aTmIL0AEQKVSLcnmFG3YYY,59
26
- audiolibrarian-0.16.5.dist-info/licenses/COPYING,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
27
- audiolibrarian-0.16.5.dist-info/licenses/LICENSE,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
28
- audiolibrarian-0.16.5.dist-info/RECORD,,