diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4aeed34..5806767 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,32 +12,31 @@ env: TIDAL_PRIVATE_CLIENT_SECRET: ${{ secrets.TIDAL_PRIVATE_CLIENT_SECRET }} jobs: build: - name: Continuous integration (Python ${{ matrix.python-version }}) + name: continuous-integration-${{ matrix.os }}-python-${{ matrix.python-version }} strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest] python-version: ["3.9", "3.10", "3.11"] runs-on: ${{ matrix.os }} defaults: run: shell: bash -el {0} timeout-minutes: 60 - steps: + steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - uses: FedericoCarboni/setup-ffmpeg@v2 id: setup-ffmpeg - - name: Install required dependencies using pip - run: | - python3 -m pip install -r requirements_minimal.txt - - name: Lint with ruff + - name: pip-install-dependencies + run: python3 -m pip install -r requirements_minimal.txt + - name: ruff-lint run: | python3 -m pip install ruff - ruff --target-version=py39 . + ruff check --target-version=py39 . continue-on-error: true - - name: Test with coverage and pytest + - name: pytest-test run: | - python3 -m pip install coverage pytest - coverage run -m pytest \ No newline at end of file + python3 -m pip install pytest + pytest \ No newline at end of file diff --git a/docs/.doctrees/api.doctree b/docs/.doctrees/api.doctree index 1eb9f07..cc5d927 100644 Binary files a/docs/.doctrees/api.doctree and b/docs/.doctrees/api.doctree differ diff --git a/docs/.doctrees/api/minim.audio.Audio.doctree b/docs/.doctrees/api/minim.audio.Audio.doctree index 6172c8e..a8821cf 100644 Binary files a/docs/.doctrees/api/minim.audio.Audio.doctree and b/docs/.doctrees/api/minim.audio.Audio.doctree differ diff --git a/docs/.doctrees/api/minim.audio.FLACAudio.doctree b/docs/.doctrees/api/minim.audio.FLACAudio.doctree index 282cd2a..a46b40f 100644 Binary files a/docs/.doctrees/api/minim.audio.FLACAudio.doctree and b/docs/.doctrees/api/minim.audio.FLACAudio.doctree differ diff --git a/docs/.doctrees/api/minim.audio.MP3Audio.doctree b/docs/.doctrees/api/minim.audio.MP3Audio.doctree index c1c6293..b23b770 100644 Binary files a/docs/.doctrees/api/minim.audio.MP3Audio.doctree and b/docs/.doctrees/api/minim.audio.MP3Audio.doctree differ diff --git a/docs/.doctrees/api/minim.audio.MP4Audio.doctree b/docs/.doctrees/api/minim.audio.MP4Audio.doctree index f26cf5d..4c46b52 100644 Binary files a/docs/.doctrees/api/minim.audio.MP4Audio.doctree and b/docs/.doctrees/api/minim.audio.MP4Audio.doctree differ diff --git a/docs/.doctrees/api/minim.audio.OGGAudio.doctree b/docs/.doctrees/api/minim.audio.OGGAudio.doctree index ff7c570..b0d39dc 100644 Binary files a/docs/.doctrees/api/minim.audio.OGGAudio.doctree and b/docs/.doctrees/api/minim.audio.OGGAudio.doctree differ diff --git a/docs/.doctrees/api/minim.audio.WAVEAudio.doctree b/docs/.doctrees/api/minim.audio.WAVEAudio.doctree index fd36b09..b01ad26 100644 Binary files a/docs/.doctrees/api/minim.audio.WAVEAudio.doctree and b/docs/.doctrees/api/minim.audio.WAVEAudio.doctree differ diff --git a/docs/.doctrees/api/minim.audio.doctree b/docs/.doctrees/api/minim.audio.doctree index 37992fd..143db08 100644 Binary files a/docs/.doctrees/api/minim.audio.doctree and b/docs/.doctrees/api/minim.audio.doctree differ diff --git a/docs/.doctrees/api/minim.discogs.API.doctree b/docs/.doctrees/api/minim.discogs.API.doctree index be6f3b1..02c7e61 100644 Binary files a/docs/.doctrees/api/minim.discogs.API.doctree and b/docs/.doctrees/api/minim.discogs.API.doctree differ diff --git a/docs/.doctrees/api/minim.discogs.doctree b/docs/.doctrees/api/minim.discogs.doctree index 3ba80cd..fd76e43 100644 Binary files a/docs/.doctrees/api/minim.discogs.doctree and b/docs/.doctrees/api/minim.discogs.doctree differ diff --git a/docs/.doctrees/api/minim.doctree b/docs/.doctrees/api/minim.doctree index 4415aed..1701081 100644 Binary files a/docs/.doctrees/api/minim.doctree and b/docs/.doctrees/api/minim.doctree differ diff --git a/docs/.doctrees/api/minim.itunes.SearchAPI.doctree b/docs/.doctrees/api/minim.itunes.SearchAPI.doctree index 61a2772..cfe58cb 100644 Binary files a/docs/.doctrees/api/minim.itunes.SearchAPI.doctree and b/docs/.doctrees/api/minim.itunes.SearchAPI.doctree differ diff --git a/docs/.doctrees/api/minim.itunes.doctree b/docs/.doctrees/api/minim.itunes.doctree index fc62aef..600f32c 100644 Binary files a/docs/.doctrees/api/minim.itunes.doctree and b/docs/.doctrees/api/minim.itunes.doctree differ diff --git a/docs/.doctrees/api/minim.qobuz.PrivateAPI.doctree b/docs/.doctrees/api/minim.qobuz.PrivateAPI.doctree index 5a8cba1..253cd6d 100644 Binary files a/docs/.doctrees/api/minim.qobuz.PrivateAPI.doctree and b/docs/.doctrees/api/minim.qobuz.PrivateAPI.doctree differ diff --git a/docs/.doctrees/api/minim.qobuz.doctree b/docs/.doctrees/api/minim.qobuz.doctree index e953de7..549572b 100644 Binary files a/docs/.doctrees/api/minim.qobuz.doctree and b/docs/.doctrees/api/minim.qobuz.doctree differ diff --git a/docs/.doctrees/api/minim.spotify.PrivateLyricsService.doctree b/docs/.doctrees/api/minim.spotify.PrivateLyricsService.doctree index 26bfc23..2cb6ddb 100644 Binary files a/docs/.doctrees/api/minim.spotify.PrivateLyricsService.doctree and b/docs/.doctrees/api/minim.spotify.PrivateLyricsService.doctree differ diff --git a/docs/.doctrees/api/minim.spotify.WebAPI.doctree b/docs/.doctrees/api/minim.spotify.WebAPI.doctree index 75663fb..fde02f8 100644 Binary files a/docs/.doctrees/api/minim.spotify.WebAPI.doctree and b/docs/.doctrees/api/minim.spotify.WebAPI.doctree differ diff --git a/docs/.doctrees/api/minim.spotify.doctree b/docs/.doctrees/api/minim.spotify.doctree index 9cbd172..020c990 100644 Binary files a/docs/.doctrees/api/minim.spotify.doctree and b/docs/.doctrees/api/minim.spotify.doctree differ diff --git a/docs/.doctrees/api/minim.tidal.API.doctree b/docs/.doctrees/api/minim.tidal.API.doctree index 2691a84..bf75de1 100644 Binary files a/docs/.doctrees/api/minim.tidal.API.doctree and b/docs/.doctrees/api/minim.tidal.API.doctree differ diff --git a/docs/.doctrees/api/minim.tidal.PrivateAPI.doctree b/docs/.doctrees/api/minim.tidal.PrivateAPI.doctree index 55d8c4d..125a395 100644 Binary files a/docs/.doctrees/api/minim.tidal.PrivateAPI.doctree and b/docs/.doctrees/api/minim.tidal.PrivateAPI.doctree differ diff --git a/docs/.doctrees/api/minim.tidal.doctree b/docs/.doctrees/api/minim.tidal.doctree index 46eaaaa..cc51766 100644 Binary files a/docs/.doctrees/api/minim.tidal.doctree and b/docs/.doctrees/api/minim.tidal.doctree differ diff --git a/docs/.doctrees/api/minim.utility.doctree b/docs/.doctrees/api/minim.utility.doctree index 7deceac..0ad8d0b 100644 Binary files a/docs/.doctrees/api/minim.utility.doctree and b/docs/.doctrees/api/minim.utility.doctree differ diff --git a/docs/.doctrees/api/minim.utility.format_multivalue.doctree b/docs/.doctrees/api/minim.utility.format_multivalue.doctree index 5ba86c1..f33b876 100644 Binary files a/docs/.doctrees/api/minim.utility.format_multivalue.doctree and b/docs/.doctrees/api/minim.utility.format_multivalue.doctree differ diff --git a/docs/.doctrees/api/minim.utility.gestalt_ratio.doctree b/docs/.doctrees/api/minim.utility.gestalt_ratio.doctree index 7c4976a..aa0b47b 100644 Binary files a/docs/.doctrees/api/minim.utility.gestalt_ratio.doctree and b/docs/.doctrees/api/minim.utility.gestalt_ratio.doctree differ diff --git a/docs/.doctrees/api/minim.utility.levenshtein_ratio.doctree b/docs/.doctrees/api/minim.utility.levenshtein_ratio.doctree index 5c5aa32..154a828 100644 Binary files a/docs/.doctrees/api/minim.utility.levenshtein_ratio.doctree and b/docs/.doctrees/api/minim.utility.levenshtein_ratio.doctree differ diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle index 1b4e154..6770b0d 100644 Binary files a/docs/.doctrees/environment.pickle and b/docs/.doctrees/environment.pickle differ diff --git a/docs/.doctrees/index.doctree b/docs/.doctrees/index.doctree index ac1bdb2..adb59d6 100644 Binary files a/docs/.doctrees/index.doctree and b/docs/.doctrees/index.doctree differ diff --git a/docs/.doctrees/notebooks/getting_started.doctree b/docs/.doctrees/notebooks/getting_started.doctree index ecd3525..125ccc9 100644 Binary files a/docs/.doctrees/notebooks/getting_started.doctree and b/docs/.doctrees/notebooks/getting_started.doctree differ diff --git a/docs/.doctrees/notebooks/user_guide/editing_audio_metadata.doctree b/docs/.doctrees/notebooks/user_guide/editing_audio_metadata.doctree index 32341d9..fd2a3c4 100644 Binary files a/docs/.doctrees/notebooks/user_guide/editing_audio_metadata.doctree and b/docs/.doctrees/notebooks/user_guide/editing_audio_metadata.doctree differ diff --git a/docs/.doctrees/notebooks/user_guide/getting_recommendations.doctree b/docs/.doctrees/notebooks/user_guide/getting_recommendations.doctree index fe70e09..4d08344 100644 Binary files a/docs/.doctrees/notebooks/user_guide/getting_recommendations.doctree and b/docs/.doctrees/notebooks/user_guide/getting_recommendations.doctree differ diff --git a/docs/.doctrees/notebooks/user_guide/transferring_music_libraries.doctree b/docs/.doctrees/notebooks/user_guide/transferring_music_libraries.doctree index 07f9bd6..474b986 100644 Binary files a/docs/.doctrees/notebooks/user_guide/transferring_music_libraries.doctree and b/docs/.doctrees/notebooks/user_guide/transferring_music_libraries.doctree differ diff --git a/docs/.doctrees/user_guide.doctree b/docs/.doctrees/user_guide.doctree index 453ddc2..130a117 100644 Binary files a/docs/.doctrees/user_guide.doctree and b/docs/.doctrees/user_guide.doctree differ diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 4451ae4..c33ca2d 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -4,7 +4,7 @@ - +
import subprocess
from typing import Any, Union
import urllib
+import warnings
from mutagen import id3, flac, mp3, mp4, oggflac, oggopus, oggvorbis, wave
-from . import utility, FOUND_FFMPEG, FFMPEG_CODECS
+from . import utility, FOUND_FFMPEG
from .qobuz import _parse_performers
+if FOUND_FFMPEG:
+ from . import FFMPEG_CODECS
+
try:
from PIL import Image
FOUND_PILLOW = True
@@ -312,7 +316,7 @@ Source code for minim.audio
}
def __init__(self, filename: str, tags: id3.ID3) -> None:
-
+
"""
Create an ID3 tag handler.
"""
@@ -329,11 +333,11 @@ Source code for minim.audio
for field, (frame, base, _) in self._FIELDS.items():
value = self._tags.getall(frame)
- if value:
+ if value:
value = ([sv for v in value for sv in getattr(v, base)]
if len(value) > 1 else getattr(value[0], base))
if list not in self._FIELDS_TYPES[field]:
- value = utility.format_multivalue(value, False,
+ value = utility.format_multivalue(value, False,
primary=True)
if not isinstance(value, self._FIELDS_TYPES[field]):
try:
@@ -344,7 +348,7 @@ Source code for minim.audio
else:
if not isinstance(value[0], self._FIELDS_TYPES[field]):
try:
- value = [self._FIELDS_TYPES[field][0](v)
+ value = [self._FIELDS_TYPES[field][0](v)
for v in value]
except ValueError:
continue
@@ -408,7 +412,7 @@ Source code for minim.audio
**{base: func(value) if func else value}
)
)
-
+
if "TXXX:comment" in self._tags:
self._tags.delall("TXXX:comment")
@@ -435,7 +439,7 @@ Source code for minim.audio
else open(self.artwork, "rb") as f:
self.artwork = f.read()
self._tags.add(
- id3.APIC(data=self.artwork,
+ id3.APIC(data=self.artwork,
mime=IMAGE_FORMATS[self._artwork_format])
)
@@ -485,7 +489,7 @@ Source code for minim.audio
}
def __init__(self, filename: str, tags: id3.ID3) -> None:
-
+
"""
Create a Vorbis comment handler.
"""
@@ -504,7 +508,7 @@ Source code for minim.audio
value = self._tags.get(key)
if value:
if list not in self._FIELDS_TYPES[field]:
- value = utility.format_multivalue(value, False,
+ value = utility.format_multivalue(value, False,
primary=True)
if type(value) not in self._FIELDS_TYPES[field]:
try:
@@ -514,7 +518,7 @@ Source code for minim.audio
else:
if type(value[0]) not in self._FIELDS_TYPES[field]:
try:
- value = [self._FIELDS_TYPES[field][0](v)
+ value = [self._FIELDS_TYPES[field][0](v)
for v in value]
except ValueError:
continue
@@ -526,7 +530,7 @@ Source code for minim.audio
self.compilation = bool(int(self._tags.get("compilation")[0])) \
if "compilation" in self._tags else None
-
+
if "discnumber" in self._tags:
disc_number = self._tags.get("discnumber")[0]
if "/" in disc_number:
@@ -617,7 +621,7 @@ Source code for minim.audio
Generic audio file handler.
Subclasses for specific audio containers or formats include
-
+
* :class:`FLACAudio` for audio encoded using the Free
Lossless Audio Codec (FLAC),
* :class:`MP3Audio` for audio encoded and stored in the MPEG Audio
@@ -628,7 +632,7 @@ Source code for minim.audio
container,
* :class:`OggAudio` for Opus or Vorbis audio stored in an Ogg file,
and
- * :class:`WAVEAudio` for audio encoded using linear pulse-code
+ * :class:`WAVEAudio` for audio encoded using linear pulse-code
modulation (LPCM) and in the Waveform Audio File Format (WAVE).
.. note::
@@ -638,14 +642,14 @@ Source code for minim.audio
there may be instances when this detection fails, especially when
the audio codec and format combination is rarely seen. As such,
it is always best to directly use one of the subclasses above to
- create a file handler for your audio file when its audio codec
+ create a file handler for your audio file when its audio codec
and format are known.
Parameters
----------
file : `str` or `pathlib.Path`
Audio filename or path.
-
+
pattern : `tuple`, keyword-only, optional
Regular expression search pattern and the corresponding metadata
field(s).
@@ -653,16 +657,16 @@ Source code for minim.audio
.. container::
**Valid values**:
-
+
The supported metadata fields are
* :code:`"artist"` for the track artist,
- * :code:`"title"` for the track title, and
+ * :code:`"title"` for the track title, and
* :code:`"track_number"` for the track number.
**Examples**:
- * :code:`("(.*) - (.*)", ("artist", "title"))` matches
+ * :code:`("(.*) - (.*)", ("artist", "title"))` matches
filenames like "Taylor Swift - Cruel Summer.flac".
* :code:`("(\\d*) - (.*)", ("track_number", "title"))` matches
filenames like "04 - The Man.m4a".
@@ -670,12 +674,12 @@ Source code for minim.audio
filenames like "13 You Need to Calm Down.mp3".
multivalue : `bool`
- Determines whether multivalue tags are supported. If
+ Determines whether multivalue tags are supported. If
:code:`False`, the items in `value` are concatenated using the
separator(s) specified in `sep`.
sep : `str` or `tuple`, keyword-only, default: :code:`(", ", " & ")`
- Separator(s) to use to concatenate multivalue tags. If a
+ Separator(s) to use to concatenate multivalue tags. If a
:code:`str` is provided, it is used to concatenate all values.
If a :code:`tuple` is provided, the first :code:`str` is used to
concatenate the first :math:`n - 1` values, and the second
@@ -685,13 +689,13 @@ Source code for minim.audio
----------
album : `str`
Album title.
-
+
album_artist : `str` or `list`
Album artist(s).
-
+
artist : `str` or `list`
Artist(s).
-
+
artwork : `bytes` or `str`
Byte-representation of, URL leading to, or filename of file
containing the cover artwork.
@@ -707,7 +711,7 @@ Source code for minim.audio
codec : `str`
Audio codec.
-
+
comment : `str`
Comment(s).
@@ -734,7 +738,7 @@ Source code for minim.audio
isrc : `str`
International Standard Recording Code (ISRC).
-
+
lyrics : `str`
Lyrics.
@@ -777,7 +781,7 @@ Source code for minim.audio
}
def __init__(
- self, file: Union[str, pathlib.Path], *,
+ self, file: Union[str, pathlib.Path], *,
pattern: tuple[str, tuple[str]] = None, multivalue: bool = False,
sep: Union[str, list[str]] = (", ", " & ")) -> None:
@@ -808,13 +812,13 @@ Source code for minim.audio
file = pathlib.Path(file)
if not file.is_file():
raise FileNotFoundError(f"'{file}' not found.")
-
+
ext = file.suffix[1:].lower()
for a in Audio.__subclasses__():
if ext in a._EXTENSIONS:
return a(*args, **kwargs)
raise TypeError(f"'{file}' has an unsupported audio format.")
-
+
return super(Audio, cls).__new__(cls)
def _from_filename(self) -> None:
@@ -827,7 +831,7 @@ Source code for minim.audio
groups = re.findall(self._pattern[0], self._file.stem)
if groups:
missing = tuple(k in {"artist", "title", "track_number"}
- and getattr(self, k) is None
+ and getattr(self, k) is None
for k in self._pattern[1])
for flag, attr, val in zip(missing, self._pattern[1], groups[0]):
if flag:
@@ -840,12 +844,12 @@ Source code for minim.audio
filename: str = None, preserve: bool = True) -> None:
"""
- Convert the current audio file to another format.
+ Convert the current audio file to another format.
.. admonition:: Software dependency
Requires `FFmpeg <https://ffmpeg.org/>`_.
-
+
.. note::
The audio file handler is automatically updated to reflect
@@ -863,22 +867,22 @@ Source code for minim.audio
**Valid values**:
- * :code:`"aac"`, :code:`"m4a"`, :code:`"mp4"`, or
+ * :code:`"aac"`, :code:`"m4a"`, :code:`"mp4"`, or
:code:`"mp4a"` for lossy AAC audio.
* :code:`"alac"` for lossless ALAC audio.
* :code:`"flac"` for lossless FLAC audio.
* :code:`"mp3"` for lossy MP3 audio.
* :code:`"ogg"` or :code:`"opus"` for lossy Opus audio
* :code:`"vorbis"` for lossy Vorbis audio.
- * :code:`"lpcm"`, :code:`"wav"`, or :code:`"wave"` for
+ * :code:`"lpcm"`, :code:`"wav"`, or :code:`"wave"` for
lossless LPCM audio.
container : `str`, optional
- New audio file container. If not specified, the best
+ New audio file container. If not specified, the best
container is determined based on `codec`.
.. container::
-
+
**Valid values**:
* :code:`"flac"` for a FLAC audio container, which only
@@ -890,12 +894,12 @@ Source code for minim.audio
supports MP3 audio.
* :code:`"ogg"` for an Ogg audio container, which
supports FLAC, Opus, and Vorbis audio.
- * :code:`"wav"` or :code:`"wave"` for an WAVE audio
+ * :code:`"wav"` or :code:`"wave"` for an WAVE audio
container, which only supports LPCM audio.
options : `str`, optional
- FFmpeg command-line options, excluding the input and output
- files, the :code:`-y` flag (to overwrite files), and the
+ FFmpeg command-line options, excluding the input and output
+ files, the :code:`-y` flag (to overwrite files), and the
:code:`-c:v copy` argument (to preserve cover art for
containers that support it).
@@ -903,24 +907,24 @@ Source code for minim.audio
**Defaults**:
- * AAC audio: :code:`"-c:a aac -b:a 256k"` (or
+ * AAC audio: :code:`"-c:a aac -b:a 256k"` (or
:code:`"-c:a libfdk_aac -b:a 256k"` if FFmpeg was
compiled with :code:`--enable-libfdk-aac`)
* ALAC audio: :code:`"-c:a alac"`
* FLAC audio: :code:`"-c:a flac"`
* MP3 audio: :code:`"-c:a libmp3lame -q:a 0"`
* Opus audio: :code:`"-c:a libopus -b:a 256k -vn"`
- * Vorbis audio:
- :code:`"-c:a vorbis -strict experimental -vn"` (or
- :code:`"-c:a libvorbis -vn"` if FFmpeg was compiled
+ * Vorbis audio:
+ :code:`"-c:a vorbis -strict experimental -vn"` (or
+ :code:`"-c:a libvorbis -vn"` if FFmpeg was compiled
with :code:`--enable-libvorbis`)
- * WAVE audio: :code:`"-c:a pcm_s16le"` or
+ * WAVE audio: :code:`"-c:a pcm_s16le"` or
:code:`"-c:a pcm_s24le"`, depending on the bit depth of
the original audio file.
filename : `str`, keyword-only, optional
Filename of the converted audio file. If not provided, the
- filename of the original audio file, but with the
+ filename of the original audio file, but with the
appropriate new extension appended, is used.
preserve : `bool`, keyword-only, default: :code:`True`
@@ -941,7 +945,7 @@ Source code for minim.audio
codec = "opus"
elif codec in "wave":
codec = "lpcm"
-
+
if container:
container = container.lower()
if container == "m4a":
@@ -950,8 +954,8 @@ Source code for minim.audio
container = "wav"
try:
- acls = next(a for a in Audio.__subclasses__()
- if codec in a._CODECS
+ acls = next(a for a in Audio.__subclasses__()
+ if codec in a._CODECS
and container in a._EXTENSIONS)
except StopIteration:
emsg = (f"{_codec} audio is incompatible with "
@@ -959,12 +963,12 @@ Source code for minim.audio
raise RuntimeError(emsg)
else:
try:
- acls = next(a for a in Audio.__subclasses__()
+ acls = next(a for a in Audio.__subclasses__()
if codec in a._CODECS)
container = acls._EXTENSIONS[0]
except StopIteration:
raise RuntimeError(f"The '{_codec}' codec is not supported.")
-
+
if ("mp4" if codec == "aac" else codec) in self.codec \
and isinstance(self, acls):
wmsg = (f"'{self._file}' already has {_codec} "
@@ -1006,7 +1010,7 @@ Source code for minim.audio
obj = acls(filename)
self.__class__ = obj.__class__
self.__dict__ = obj.__dict__ | {
- key: value for (key, value) in self.__dict__.items()
+ key: value for (key, value) in self.__dict__.items()
if key in self._FIELDS_TYPES
}
@@ -1014,7 +1018,7 @@ Source code for minim.audio
[docs]
def set_metadata_using_itunes(
- self, data: dict[str, Any], *, album_data: dict[str, Any] = None,
+ self, data: dict[str, Any], *, album_data: dict[str, Any] = None,
artwork_size: Union[int, str] = 1400, artwork_format: str = "jpg",
overwrite: bool = False) -> None:
@@ -1025,15 +1029,15 @@ Source code for minim.audio
----------
data : `dict`
Information about the track in JSON format obtained using
- the iTunes Search API via
+ the iTunes Search API via
:meth:`minim.itunes.SearchAPI.search` or
:meth:`minim.itunes.SearchAPI.lookup`.
album_data : `dict`, keyword-only, optional
Information about the track's album in JSON format obtained
- using the iTunes Search API via
- :meth:`minim.itunes.SearchAPI.search` or
- :meth:`minim.itunes.SearchAPI.lookup`. If not provided,
+ using the iTunes Search API via
+ :meth:`minim.itunes.SearchAPI.search` or
+ :meth:`minim.itunes.SearchAPI.lookup`. If not provided,
album artist and copyright information is unavailable.
artwork_size : `int` or `str`, keyword-only, default: :code:`1400`
@@ -1078,11 +1082,18 @@ Source code for minim.audio
with urllib.request.urlopen(self.artwork) as r:
self.artwork = r.read()
if self._artwork_format == "tif":
- with Image.open(BytesIO(self.artwork)) as a:
- with BytesIO() as b:
- a.save(b, format="png")
- self.artwork = b.getvalue()
- self._artwork_format = "png"
+ if FOUND_PILLOW:
+ with Image.open(BytesIO(self.artwork)) as a:
+ with BytesIO() as b:
+ a.save(b, format="png")
+ self.artwork = b.getvalue()
+ self._artwork_format = "png"
+ else:
+ wmsg = ("The Pillow library is required to process "
+ "TIFF images, but was not found. No artwork "
+ "will be embedded for the current track.")
+ warnings.warn(wmsg)
+ self.artwork = self._artwork_format = None
if self.compilation is None or overwrite:
self.compilation = self.album_artist == "Various Artists"
if "releaseDate" in data and (self.date is None or overwrite):
@@ -1091,7 +1102,7 @@ Source code for minim.audio
self.disc_number = data["discNumber"]
if self.disc_count is None or overwrite:
self.disc_count = data["discCount"]
- if self.genre is None or overwrite:
+ if self.genre is None or overwrite:
self.genre = data["primaryGenreName"]
if self.title is None or overwrite:
self.title = max(data["trackName"], data["trackCensoredName"])
@@ -1168,7 +1179,7 @@ Source code for minim.audio
self.album_artist = data["album"]["artist"]["name"]
credits = _parse_performers(
- data["performers"],
+ data["performers"],
roles=["MainArtist", "FeaturedArtist", "Composers"]
)
if self.artist is None or overwrite:
@@ -1185,8 +1196,8 @@ Source code for minim.audio
self.comment = comment
if self.composer is None or overwrite:
self.composer = (
- credits.get("composers")
- or (data["composer"]["name"] if hasattr(data, "composer")
+ credits.get("composers")
+ or (data["composer"]["name"] if hasattr(data, "composer")
else None)
)
if self.copyright is None or overwrite:
@@ -1197,11 +1208,11 @@ Source code for minim.audio
else datetime.datetime.strptime(dt, "%Y-%m-%d") if isinstance(dt, str)
else datetime.datetime.max for dt in (
data.get(k) for k in {
- "release_date_original",
- "release_date_download",
- "release_date_stream",
+ "release_date_original",
+ "release_date_download",
+ "release_date_stream",
"release_date_purchase",
- "purchasable_at",
+ "purchasable_at",
"streamable_at"
}
)
@@ -1224,7 +1235,7 @@ Source code for minim.audio
utility.format_multivalue(feat_artist, False)
)
if data["version"]:
- self.title += (" [{}]" if "(" in self.title
+ self.title += (" [{}]" if "(" in self.title
else " ({})").format(data['version'])
self.title = self.title.replace(" ", " ")
if self.track_number is None or overwrite:
@@ -1243,10 +1254,10 @@ Source code for minim.audio
[docs]
def set_metadata_using_spotify(
- self, data: dict[str, Any], *,
- audio_features: dict[str, Any] = None,
+ self, data: dict[str, Any], *,
+ audio_features: dict[str, Any] = None,
lyrics: Union[str, dict[str, Any]] = None, overwrite: bool = False
- ) -> None:
+ ) -> None:
"""
Populate tags using data retrieved from the Spotify Web API
@@ -1256,7 +1267,7 @@ Source code for minim.audio
----------
data : `dict`
Information about the track in JSON format obtained using
- the Spotify Web API via
+ the Spotify Web API via
:meth:`minim.spotify.WebAPI.get_track`.
audio_features : `dict`, keyword-only, optional
@@ -1267,7 +1278,7 @@ Source code for minim.audio
lyrics : `str` or `dict`, keyword-only
Information about the track's formatted or time-synced
- lyrics obtained using the Spotify Lyrics service via
+ lyrics obtained using the Spotify Lyrics service via
:meth:`minim.spotify.PrivateLyricsService.get_lyrics`. If not
provided, lyrics are unavailable.
@@ -1297,7 +1308,7 @@ Source code for minim.audio
self.isrc = data["external_ids"]["isrc"]
if (self.lyrics is None or overwrite) and lyrics:
self.lyrics = lyrics if isinstance(lyrics, str) \
- else "\n".join(line["words"]
+ else "\n".join(line["words"]
for line in lyrics["lyrics"]["lines"])
if (self.tempo is None or overwrite) and audio_features:
self.tempo = round(audio_features["tempo"])
@@ -1335,22 +1346,22 @@ Source code for minim.audio
using the TIDAL API via :meth:`minim.tidal.API.get_album`,
:meth:`minim.tidal.API.search`,
:meth:`minim.tidal.PrivateAPI.get_album`, or
- :meth:`minim.tidal.PrivateAPI.search`. If not provided,
- album artist and disc and track numbering information is
+ :meth:`minim.tidal.PrivateAPI.search`. If not provided,
+ album artist and disc and track numbering information is
unavailable.
artwork_size : `int`, keyword-only, default: :code:`1280`
Maximum artwork size in pixels.
- **Valid values**: `artwork_size` should be between
+ **Valid values**: `artwork_size` should be between
:code:`80` and :code:`1280`.
-
+
composers : `str`, `list`, or `dict`, keyword-only, optional
- Information about the track's composers in a formatted
+ Information about the track's composers in a formatted
`str`, a `list`, or a `dict` obtained using the TIDAL API
via :meth:`minim.tidal.PrivateAPI.get_track_composers`,
:meth:`minim.tidal.PrivateAPI.get_track_contributors`, or
- :meth:`minim.tidal.PrivateAPI.get_track_credits`. If not
+ :meth:`minim.tidal.PrivateAPI.get_track_credits`. If not
provided, songwriting credits are unavailable.
lyrics : `str` or `dict`, keyword-only, optional
@@ -1373,7 +1384,7 @@ Source code for minim.audio
if (self.composer is None or overwrite) and composers:
COMPOSER_TYPES = {"Composer", "Lyricist", "Writer"}
if isinstance(composers, dict):
- self.composer = sorted({c["name"] for c in composers["items"]
+ self.composer = sorted({c["name"] for c in composers["items"]
if c["role"] in COMPOSER_TYPES})
elif isinstance(composers[0], dict):
self.composer = sorted({
@@ -1403,19 +1414,19 @@ Source code for minim.audio
image_urls = sorted(data["album"]["imageCover"],
key=lambda x: x["width"], reverse=True)
self.artwork = (
- image_urls[-1]["url"]
+ image_urls[-1]["url"]
if artwork_size < image_urls[-1]["width"]
- else next(u["url"] for u in image_urls
+ else next(u["url"] for u in image_urls
if u["width"] <= artwork_size)
)
self._artwork_format = pathlib.Path(self.artwork).suffix[1:]
else:
if self.artist is None or overwrite:
- self.artist = [a["name"] for a in data["artists"]
+ self.artist = [a["name"] for a in data["artists"]
if a["type"] == "MAIN"]
if self.artwork is None or overwrite:
artwork_size = (
- 80 if artwork_size < 80
+ 80 if artwork_size < 80
else next(s for s in [1280, 1080, 750, 640, 320, 160, 80]
if s <= artwork_size)
)
@@ -1436,14 +1447,14 @@ Source code for minim.audio
if "barcodeId" in album_data:
if self.album_artist is None or overwrite:
- self.album_artist = [a["name"] for a in album_data["artists"]
+ self.album_artist = [a["name"] for a in album_data["artists"]
if a["main"]]
if self.date is None or overwrite:
self.date = f"{album_data['releaseDate']}T00:00:00Z"
else:
if self.album_artist is None or overwrite:
self.album_artist = [
- a["name"] for a in album_data["artists"]
+ a["name"] for a in album_data["artists"]
if a["type"] == "MAIN"
]
@@ -1458,7 +1469,7 @@ Source code for minim.audio
.. seealso::
- For a full list of attributes and their descriptions, see
+ For a full list of attributes and their descriptions, see
:class:`Audio`.
Parameters
@@ -1473,16 +1484,16 @@ Source code for minim.audio
.. container::
**Valid values**:
-
+
The supported metadata fields are
* :code:`"artist"` for the track artist,
- * :code:`"title"` for the track title, and
+ * :code:`"title"` for the track title, and
* :code:`"track_number"` for the track number.
**Examples**:
- * :code:`("(.*) - (.*)", ("artist", "title"))` matches
+ * :code:`("(.*) - (.*)", ("artist", "title"))` matches
filenames like "Taylor Swift - Fearless.flac".
* :code:`("(\\d*) - (.*)", ("track_number", "title"))` matches
filenames like "03 - Love Story.flac".
@@ -1490,12 +1501,12 @@ Source code for minim.audio
filenames like "06 You Belong with Me.flac".
multivalue : `bool`
- Determines whether multivalue tags are supported. If
+ Determines whether multivalue tags are supported. If
:code:`False`, the items in `value` are concatenated using the
separator(s) specified in `sep`.
sep : `str` or `tuple`, keyword-only, default: :code:`(", ", " & ")`
- Separator(s) to use to concatenate multivalue tags. If a
+ Separator(s) to use to concatenate multivalue tags. If a
:code:`str` is provided, it is used to concatenate all values.
If a :code:`tuple` is provided, the first :code:`str` is used to
concatenate the first :math:`n - 1` values, and the second
@@ -1506,7 +1517,7 @@ Source code for minim.audio
_EXTENSIONS = ["flac"]
def __init__(
- self, file: Union[str, pathlib.Path], *,
+ self, file: Union[str, pathlib.Path], *,
pattern: tuple[str, tuple[str]] = None, multivalue: bool = False,
sep: Union[str, list[str]] = (", ", " & ")) -> None:
@@ -1514,7 +1525,7 @@ Source code for minim.audio
Create a FLAC audio file handler.
"""
- Audio.__init__(self, file, pattern=pattern, multivalue=multivalue,
+ Audio.__init__(self, file, pattern=pattern, multivalue=multivalue,
sep=sep)
self._handle = flac.FLAC(file)
if self._handle.tags is None:
@@ -1538,7 +1549,7 @@ Source code for minim.audio
.. seealso::
- For a full list of attributes and their descriptions, see
+ For a full list of attributes and their descriptions, see
:class:`Audio`.
Parameters
@@ -1553,16 +1564,16 @@ Source code for minim.audio
.. container::
**Valid values**:
-
+
The supported metadata fields are
* :code:`"artist"` for the track artist,
- * :code:`"title"` for the track title, and
+ * :code:`"title"` for the track title, and
* :code:`"track_number"` for the track number.
**Examples**:
- * :code:`("(.*) - (.*)", ("artist", "title"))` matches
+ * :code:`("(.*) - (.*)", ("artist", "title"))` matches
filenames like "Taylor Swift - Red.mp3".
* :code:`("(\\d*) - (.*)", ("track_number", "title"))` matches
filenames like "04 - I Knew You Were Trouble.mp3".
@@ -1570,12 +1581,12 @@ Source code for minim.audio
filenames like "06 22.mp3".
multivalue : `bool`
- Determines whether multivalue tags are supported. If
+ Determines whether multivalue tags are supported. If
:code:`False`, the items in `value` are concatenated using the
separator(s) specified in `sep`.
sep : `str` or `tuple`, keyword-only, default: :code:`(", ", " & ")`
- Separator(s) to use to concatenate multivalue tags. If a
+ Separator(s) to use to concatenate multivalue tags. If a
:code:`str` is provided, it is used to concatenate all values.
If a :code:`tuple` is provided, the first :code:`str` is used to
concatenate the first :math:`n - 1` values, and the second
@@ -1586,7 +1597,7 @@ Source code for minim.audio
_EXTENSIONS = ["mp3"]
def __init__(
- self, file: Union[str, pathlib.Path], *,
+ self, file: Union[str, pathlib.Path], *,
pattern: tuple[str, tuple[str]] = None, multivalue: bool = False,
sep: Union[str, list[str]] = (", ", " & ")) -> None:
@@ -1617,7 +1628,7 @@ Source code for minim.audio
.. seealso::
- For a full list of attributes and their descriptions, see
+ For a full list of attributes and their descriptions, see
:class:`Audio`.
Parameters
@@ -1632,16 +1643,16 @@ Source code for minim.audio
.. container::
**Valid values**:
-
+
The supported metadata fields are
* :code:`"artist"` for the track artist,
- * :code:`"title"` for the track title, and
+ * :code:`"title"` for the track title, and
* :code:`"track_number"` for the track number.
**Examples**:
- * :code:`("(.*) - (.*)", ("artist", "title"))` matches
+ * :code:`("(.*) - (.*)", ("artist", "title"))` matches
filenames like "Taylor Swift - Mine.m4a".
* :code:`("(\\d*) - (.*)", ("track_number", "title"))` matches
filenames like "04 - Speak Now.m4a".
@@ -1649,12 +1660,12 @@ Source code for minim.audio
filenames like "07 The Story of Us.m4a".
multivalue : `bool`
- Determines whether multivalue tags are supported. If
+ Determines whether multivalue tags are supported. If
:code:`False`, the items in `value` are concatenated using the
separator(s) specified in `sep`.
sep : `str` or `tuple`, keyword-only, default: :code:`(", ", " & ")`
- Separator(s) to use to concatenate multivalue tags. If a
+ Separator(s) to use to concatenate multivalue tags. If a
:code:`str` is provided, it is used to concatenate all values.
If a :code:`tuple` is provided, the first :code:`str` is used to
concatenate the first :math:`n - 1` values, and the second
@@ -1686,14 +1697,14 @@ Source code for minim.audio
) | dict.fromkeys(["png", 14], mp4.MP4Cover.FORMAT_PNG)
def __init__(
- self, file: Union[str, pathlib.Path], *,
+ self, file: Union[str, pathlib.Path], *,
pattern: tuple[str, tuple[str]] = None, multivalue: bool = False,
sep: Union[str, list[str]] = (", ", " & ")) -> None:
-
+
"""
Create a MP4 audio file handler.
"""
-
+
super().__init__(file, pattern=pattern, multivalue=multivalue, sep=sep)
self._handle = mp4.MP4(file)
@@ -1718,7 +1729,7 @@ Source code for minim.audio
value = self._handle.get(key)
if value:
if list not in self._FIELDS_TYPES[field]:
- value = utility.format_multivalue(value, False,
+ value = utility.format_multivalue(value, False,
primary=True)
if type(value) not in self._FIELDS_TYPES[field]:
try:
@@ -1728,7 +1739,7 @@ Source code for minim.audio
else:
if type(value[0]) not in self._FIELDS_TYPES[field]:
try:
- value = [self._FIELDS_TYPES[field][0](v)
+ value = [self._FIELDS_TYPES[field][0](v)
for v in value]
except ValueError:
continue
@@ -1745,14 +1756,14 @@ Source code for minim.audio
self.disc_number, self.disc_count = self._handle.get("disk")[0]
else:
self.disc_number = self.disc_count = None
-
+
if "trkn" in self._handle:
self.track_number, self.track_count = self._handle.get("trkn")[0]
else:
self.track_number = self.track_count = None
if "covr" in self._handle:
- self.artwork = utility.format_multivalue(self._handle.get("covr"),
+ self.artwork = utility.format_multivalue(self._handle.get("covr"),
False, primary=True)
self._artwork_format = str(
self._IMAGE_FORMATS[self.artwork.imageformat]
@@ -1784,12 +1795,12 @@ Source code for minim.audio
self._handle["----:com.apple.iTunes:ISRC"] = self.isrc.encode()
if self.disc_number or self.disc_count:
- self._handle["disk"] = [(self.disc_number or 0,
+ self._handle["disk"] = [(self.disc_number or 0,
self.disc_count or 0)]
if self.track_number or self.track_count:
- self._handle["trkn"] = [(self.track_number or 0,
+ self._handle["trkn"] = [(self.track_number or 0,
self.track_count or 0)]
-
+
if self.artwork:
if isinstance(self.artwork, str):
with urllib.request.urlopen(self.artwork) \
@@ -1798,7 +1809,7 @@ Source code for minim.audio
self.artwork = f.read()
self._handle["covr"] = [
mp4.MP4Cover(
- self.artwork,
+ self.artwork,
imageformat=self._IMAGE_FORMATS[self._artwork_format]
)
]
@@ -1816,7 +1827,7 @@ Source code for minim.audio
.. seealso::
- For a full list of attributes and their descriptions, see
+ For a full list of attributes and their descriptions, see
:class:`Audio`.
Parameters
@@ -1825,7 +1836,7 @@ Source code for minim.audio
Ogg audio filename or path.
codec : `str`, optional
- Audio codec. If not specified, it will be determined
+ Audio codec. If not specified, it will be determined
automatically.
**Valid values**: :code:`"flac"`, :code:`"opus"`, or
@@ -1838,16 +1849,16 @@ Source code for minim.audio
.. container::
**Valid values**:
-
+
The supported metadata fields are
* :code:`"artist"` for the track artist,
- * :code:`"title"` for the track title, and
+ * :code:`"title"` for the track title, and
* :code:`"track_number"` for the track number.
**Examples**:
- * :code:`("(.*) - (.*)", ("artist", "title"))` matches
+ * :code:`("(.*) - (.*)", ("artist", "title"))` matches
filenames like "Taylor Swift - Blank Space.ogg".
* :code:`("(\\d*) - (.*)", ("track_number", "title"))` matches
filenames like "03 - Style.ogg".
@@ -1855,12 +1866,12 @@ Source code for minim.audio
filenames like "06 Shake It Off.ogg".
multivalue : `bool`
- Determines whether multivalue tags are supported. If
+ Determines whether multivalue tags are supported. If
:code:`False`, the items in `value` are concatenated using the
separator(s) specified in `sep`.
sep : `str` or `tuple`, keyword-only, default: :code:`(", ", " & ")`
- Separator(s) to use to concatenate multivalue tags. If a
+ Separator(s) to use to concatenate multivalue tags. If a
:code:`str` is provided, it is used to concatenate all values.
If a :code:`tuple` is provided, the first :code:`str` is used to
concatenate the first :math:`n - 1` values, and the second
@@ -1868,18 +1879,18 @@ Source code for minim.audio
"""
_CODECS = {"flac": {"ffmpeg": "-c:a flac", "mutagen": oggflac.OggFLAC},
- "opus": {"ffmpeg": "-b:a 256k -c:a libopus -vn",
+ "opus": {"ffmpeg": "-b:a 256k -c:a libopus -vn",
"mutagen": oggopus.OggOpus},
"vorbis": {"ffmpeg": f"-c:a {FFMPEG_CODECS['vorbis']} -vn",
"mutagen": oggvorbis.OggVorbis}}
_EXTENSIONS = ["ogg", "oga", "opus"]
def __init__(
- self, file: Union[str, pathlib.Path], codec: str = None, *,
+ self, file: Union[str, pathlib.Path], codec: str = None, *,
pattern: tuple[str, tuple[str]] = None, multivalue: bool = False,
sep: Union[str, list[str]] = (", ", " & ")) -> None:
-
- Audio.__init__(self, file, pattern=pattern, multivalue=multivalue,
+
+ Audio.__init__(self, file, pattern=pattern, multivalue=multivalue,
sep=sep)
if codec and codec in self._CODECS:
@@ -1902,7 +1913,7 @@ Source code for minim.audio
self.channel_count = self._handle.info.channels
if self.codec == "flac":
self.bit_depth = self._handle.info.bits_per_sample
- self.sample_rate = self._handle.info.sample_rate
+ self.sample_rate = self._handle.info.sample_rate
self.bitrate = self.bit_depth * self.channel_count \
* self.sample_rate
elif self.codec == "opus":
@@ -1922,7 +1933,7 @@ Source code for minim.audio
.. seealso::
- For a full list of attributes and their descriptions, see
+ For a full list of attributes and their descriptions, see
:class:`Audio`.
Parameters
@@ -1937,16 +1948,16 @@ Source code for minim.audio
.. container::
**Valid values**:
-
+
The supported metadata fields are
* :code:`"artist"` for the track artist,
- * :code:`"title"` for the track title, and
+ * :code:`"title"` for the track title, and
* :code:`"track_number"` for the track number.
**Examples**:
- * :code:`("(.*) - (.*)", ("artist", "title"))` matches
+ * :code:`("(.*) - (.*)", ("artist", "title"))` matches
filenames like "Taylor Swift - Don't Blame Me.wav".
* :code:`("(\\d*) - (.*)", ("track_number", "title"))` matches
filenames like "05 - Delicate.wav".
@@ -1954,12 +1965,12 @@ Source code for minim.audio
filenames like "06 Look What You Made Me Do.wav".
multivalue : `bool`
- Determines whether multivalue tags are supported. If
+ Determines whether multivalue tags are supported. If
:code:`False`, the items in `value` are concatenated using the
separator(s) specified in `sep`.
sep : `str` or `tuple`, keyword-only, default: :code:`(", ", " & ")`
- Separator(s) to use to concatenate multivalue tags. If a
+ Separator(s) to use to concatenate multivalue tags. If a
:code:`str` is provided, it is used to concatenate all values.
If a :code:`tuple` is provided, the first :code:`str` is used to
concatenate the first :math:`n - 1` values, and the second
@@ -1970,7 +1981,7 @@ Source code for minim.audio
_EXTENSIONS = ["wav"]
def __init__(
- self, file: Union[str, pathlib.Path], *,
+ self, file: Union[str, pathlib.Path], *,
pattern: tuple[str, tuple[str]] = None, multivalue: bool = False,
sep: Union[str, list[str]] = (", ", " & ")) -> None:
diff --git a/docs/_modules/minim/discogs.html b/docs/_modules/minim/discogs.html
index 5203fc6..6b6a678 100644
--- a/docs/_modules/minim/discogs.html
+++ b/docs/_modules/minim/discogs.html
@@ -4,7 +4,7 @@
-
+
minim.discogs - Minim 1.0.0 documentation
@@ -245,6 +245,8 @@ Source code for minim.discogs
Discogs
=======
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>
+
+This module contains a complete implementation of the Discogs API.
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
@@ -262,7 +264,7 @@ Source code for minim.discogs
import requests
-from . import (FOUND_FLASK, FOUND_PLAYWRIGHT, VERSION, REPOSITORY_URL,
+from . import (FOUND_FLASK, FOUND_PLAYWRIGHT, VERSION, REPOSITORY_URL,
DIR_HOME, DIR_TEMP, _config)
if FOUND_FLASK:
from flask import Flask, request
@@ -272,13 +274,13 @@ Source code for minim.discogs
__all__ = ["API"]
class _DiscogsRedirectHandler(BaseHTTPRequestHandler):
-
+
"""
HTTP request handler for the Discogs OAuth 1.0a flow.
"""
def do_GET(self):
-
+
"""
Handles an incoming GET request and parses the query string.
"""
@@ -291,7 +293,7 @@ Source code for minim.discogs
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
- status = "denied" if "denied" in self.server.response else "granted"
+ status = "denied" if "denied" in self.server.response else "granted"
self.wfile.write(
f"Access {status}. You may close this page now.".encode()
)
@@ -303,7 +305,7 @@ Source code for minim.discogs
"""
Discogs API client.
- The Discogs API lets developers build their own Discogs-powered
+ The Discogs API lets developers build their own Discogs-powered
applications for the web, desktop, and mobile devices. It is a
RESTful interface to Discogs data and enables accessing JSON-
formatted information about artists, releases, and labels,
@@ -312,32 +314,32 @@ Source code for minim.discogs
.. seealso::
- For more information, see the `Discogs API home page
+ For more information, see the `Discogs API home page
<https://www.discogs.com/developers>`_.
The Discogs API can be accessed with or without authentication.
- (client credentials, personal access token, or OAuth access token
+ (client credentials, personal access token, or OAuth access token
and access token secret). However, it is recommended that users at
- least provide client credentials to enjoy higher rate limits and
- access to image URLs. The consumer key and consumer secret can
- either be provided to this class's constructor as keyword arguments
- or be stored as :code:`DISCOGS_CONSUMER_KEY` and
- :code:`DISCOGS_CONSUMER_SECRET` in the operating system's
- environment variables.
-
+ least provide client credentials to enjoy higher rate limits and
+ access to image URLs. The consumer key and consumer secret can
+ either be provided to this class's constructor as keyword arguments
+ or be stored as :code:`DISCOGS_CONSUMER_KEY` and
+ :code:`DISCOGS_CONSUMER_SECRET` in the operating system's
+ environment variables.
+
.. seealso::
To get client credentials, see the Registration section of the
`Authentication page <https://www.discogs.com/developers
- /#page:authentication>`_ of the Discogs API website. To take
- advantage of Minim's automatic access token retrieval
+ /#page:authentication>`_ of the Discogs API website. To take
+ advantage of Minim's automatic access token retrieval
functionality for the OAuth 1.0a flow, the redirect URI should be
- in the form :code:`http://localhost:{port}/callback`, where
+ in the form :code:`http://localhost:{port}/callback`, where
:code:`{port}` is an open port on :code:`localhost`.
-
+
To view and make changes to account information and resources, users
- must either provide a personal access token to this class's
- constructor as a keyword argument or undergo the OAuth 1.0a flow,
+ must either provide a personal access token to this class's
+ constructor as a keyword argument or undergo the OAuth 1.0a flow,
which require valid client credentials, using Minim. If an existing
OAuth access token/secret pair is available, it can be provided to
this class's constructor as keyword arguments to bypass the access
@@ -349,9 +351,9 @@ Source code for minim.discogs
at any time using :meth:`set_flow` and :meth:`set_access_token`,
respectively.
- Minim also stores and manages access tokens and their properties.
- When the OAuth 1.0a flow is used to acquire an access token/secret
- pair, it is automatically saved to the Minim configuration file to
+ Minim also stores and manages access tokens and their properties.
+ When the OAuth 1.0a flow is used to acquire an access token/secret
+ pair, it is automatically saved to the Minim configuration file to
be loaded on the next instantiation of this class. This behavior can
be disabled if there are any security concerns, like if the computer
being used is a shared device.
@@ -360,20 +362,20 @@ Source code for minim.discogs
----------
consumer_key : `str`, keyword-only, optional
Consumer key. Required for the OAuth 1.0a flow, and can be used
- in the Discogs authorization flow alongside a consumer secret.
- If it is not stored as :code:`DISCOGS_CONSUMER_KEY` in the
+ in the Discogs authorization flow alongside a consumer secret.
+ If it is not stored as :code:`DISCOGS_CONSUMER_KEY` in the
operating system's environment variables or found in the Minim
configuration file, it can be provided here.
consumer_secret : `str`, keyword-only, optional
Consumer secret. Required for the OAuth 1.0a flow, and can be
used in the Discogs authorization flow alongside a consumer key.
- If it is not stored as :code:`DISCOGS_CONSUMER_SECRET` in the
+ If it is not stored as :code:`DISCOGS_CONSUMER_SECRET` in the
operating system's environment variables or found in the Minim
configuration file, it can be provided here.
flow : `str`, keyword-only, optional
- Authorization flow. If :code:`None` and no access token is
+ Authorization flow. If :code:`None` and no access token is
provided, no user authentication will be performed and client
credentials will not be attached to requests, even if found or
provided.
@@ -388,13 +390,13 @@ Source code for minim.discogs
browser : `bool`, keyword-only, default: :code:`False`
Determines whether a web browser is automatically opened for the
- OAuth 1.0a flow. If :code:`False`, users will have to manually
+ OAuth 1.0a flow. If :code:`False`, users will have to manually
open the authorization URL and provide the full callback URI via
the terminal.
web_framework : `str`, keyword-only, optional
Determines which web framework to use for the OAuth 1.0a flow.
-
+
.. container::
**Valid values**:
@@ -402,22 +404,22 @@ Source code for minim.discogs
* :code:`"http.server"` for the built-in implementation of
HTTP servers.
* :code:`"flask"` for the Flask framework.
- * :code:`"playwright"` for the Playwright framework by
+ * :code:`"playwright"` for the Playwright framework by
Microsoft.
port : `int` or `str`, keyword-only, default: :code:`8888`
Port on :code:`localhost` to use for the OAuth 1.0a flow with
- the :code:`http.server` and Flask frameworks. Only used if
+ the :code:`http.server` and Flask frameworks. Only used if
`redirect_uri` is not specified.
redirect_uri : `str`, keyword-only, optional
- Redirect URI for the OAuth 1.0a flow. If not on
+ Redirect URI for the OAuth 1.0a flow. If not on
:code:`localhost`, the automatic request access token retrieval
functionality is not available.
access_token : `str`, keyword-only, optional
Personal or OAuth access token. If provided here or found in the
- Minim configuration file, the authentication process is
+ Minim configuration file, the authentication process is
bypassed.
access_token_secret : `str`, keyword-only, optional
@@ -429,7 +431,7 @@ Source code for minim.discogs
save : `bool`, keyword-only, default: :code:`True`
Determines whether newly obtained access tokens and their
- associated properties are stored to the Minim configuration
+ associated properties are stored to the Minim configuration
file.
Attributes
@@ -445,7 +447,7 @@ Source code for minim.discogs
REQUEST_TOKEN_URL : `str`
URL for the OAuth 1.0a request token endpoint.
-
+
session : `requests.Session`
Session used to send requests to the Discogs API.
"""
@@ -460,11 +462,11 @@ Source code for minim.discogs
def __init__(
self, *, consumer_key: str = None, consumer_secret: str = None,
- flow: str = None, browser: bool = False, web_framework: str = None,
- port: Union[int, str] = 8888, redirect_uri: str = None,
- access_token: str = None, access_token_secret: str = None,
+ flow: str = None, browser: bool = False, web_framework: str = None,
+ port: Union[int, str] = 8888, redirect_uri: str = None,
+ access_token: str = None, access_token_secret: str = None,
overwrite: bool = False, save: bool = True) -> None:
-
+
"""
Create a Discogs API client.
"""
@@ -472,7 +474,7 @@ Source code for minim.discogs
self.session = requests.Session()
self.session.headers["User-Agent"] = f"Minim/{VERSION} +{REPOSITORY_URL}"
- if (access_token is None and _config.has_section(self._NAME)
+ if (access_token is None and _config.has_section(self._NAME)
and not overwrite):
flow = _config.get(self._NAME, "flow")
access_token = _config.get(self._NAME, "access_token")
@@ -484,7 +486,7 @@ Source code for minim.discogs
self.set_flow(
flow, consumer_key=consumer_key, consumer_secret=consumer_secret,
- browser=browser, web_framework=web_framework, port=port,
+ browser=browser, web_framework=web_framework, port=port,
redirect_uri=redirect_uri, save=save
)
self.set_access_token(access_token, access_token_secret)
@@ -507,8 +509,8 @@ Source code for minim.discogs
"""
if token and (
- self._flow != "oauth"
- or self._flow == "discogs"
+ self._flow != "oauth"
+ or self._flow == "discogs"
and "token" not in self.session.headers["Authorization"]
):
emsg = (f"{self._NAME}.{endpoint}() requires user "
@@ -521,14 +523,14 @@ Source code for minim.discogs
def _get_json(self, url: str, **kwargs) -> dict:
"""
- Send a GET request and return the JSON-encoded content of the
+ Send a GET request and return the JSON-encoded content of the
response.
Parameters
----------
url : `str`
URL for the GET request.
-
+
**kwargs
Keyword arguments to pass to :meth:`requests.request`.
@@ -541,7 +543,7 @@ Source code for minim.discogs
return self._request("get", url, **kwargs).json()
def _request(
- self, method: str, url: str, *, oauth: dict[str, Any] = None,
+ self, method: str, url: str, *, oauth: dict[str, Any] = None,
**kwargs) -> requests.Response:
"""
@@ -574,7 +576,7 @@ Source code for minim.discogs
if oauth is None:
oauth = {}
oauth = self._oauth | {
- "oauth_nonce": secrets.token_hex(32),
+ "oauth_nonce": secrets.token_hex(32),
"oauth_timestamp": f"{time.time():.0f}"
} | oauth
kwargs["headers"]["Authorization"] = "OAuth " + ", ".join(
@@ -616,7 +618,7 @@ Source code for minim.discogs
oauth["oauth_callback"] = self._redirect_uri
r = self._request(
"get",
- self.REQUEST_TOKEN_URL,
+ self.REQUEST_TOKEN_URL,
headers={
"Content-Type": "application/x-www-form-urlencoded"
},
@@ -627,13 +629,13 @@ Source code for minim.discogs
if self._web_framework == "playwright":
har_file = DIR_TEMP / "minim_discogs.har"
-
+
with sync_playwright() as playwright:
browser = playwright.firefox.launch(headless=False)
context = browser.new_context(record_har_path=har_file)
page = context.new_page()
page.goto(auth_url, timeout=0)
- page.wait_for_url(f"{self._redirect_uri}*",
+ page.wait_for_url(f"{self._redirect_uri}*",
wait_until="commit")
context.close()
browser.close()
@@ -642,7 +644,7 @@ Source code for minim.discogs
oauth |= dict(
urllib.parse.parse_qsl(
urllib.parse.urlparse(
- re.search(fr'{self._redirect_uri}\?(.*?)"',
+ re.search(fr'{self._redirect_uri}\?(.*?)"',
f.read()).group(0)
).query
)
@@ -658,7 +660,7 @@ Source code for minim.discogs
f"in your web browser:\n\n{auth_url}\n")
if self._web_framework == "http.server":
- httpd = HTTPServer(("", self._port),
+ httpd = HTTPServer(("", self._port),
_DiscogsRedirectHandler)
httpd.handle_request()
oauth |= httpd.response
@@ -677,7 +679,7 @@ Source code for minim.discogs
return ("Access granted. You may close "
"this page now.")
- server = Process(target=app.run,
+ server = Process(target=app.run,
args=("0.0.0.0", self._port))
server.start()
while not json_file.is_file():
@@ -721,7 +723,7 @@ Source code for minim.discogs
}
with open(DIR_HOME / "minim.cfg", "w") as f:
_config.write(f)
-
+
self._oauth |= {
"oauth_token": access_token,
"oauth_signature": self._consumer_secret
@@ -740,9 +742,9 @@ Source code for minim.discogs
else:
self.session.headers["Authorization"] = \
f"Discogs token={access_token}"
-
- if (self._flow == "oauth"
- or self._flow == "discogs"
+
+ if (self._flow == "oauth"
+ or self._flow == "discogs"
and "token" in self.session.headers["Authorization"]):
identity = self.get_identity()
self._username = identity["username"]
@@ -763,7 +765,7 @@ Source code for minim.discogs
----------
flow : `str`
Authorization flow. If :code:`None`, no user authentication
- will be performed and client credentials will not be
+ will be performed and client credentials will not be
attached to requests, even if found or provided.
.. container::
@@ -783,10 +785,10 @@ Source code for minim.discogs
consumer_secret : `str`, keyword-only, optional
Consumer secret. Required for the OAuth 1.0a flow, and can
- be used in the Discogs authorization flow alongside a
- consumer key. If it is not stored as
- :code:`DISCOGS_CONSUMER_SECRET` in the operating system's
- environment variables or found in the Minim configuration
+ be used in the Discogs authorization flow alongside a
+ consumer key. If it is not stored as
+ :code:`DISCOGS_CONSUMER_SECRET` in the operating system's
+ environment variables or found in the Minim configuration
file, it can be provided here.
browser : `bool`, keyword-only, default: :code:`False`
@@ -806,7 +808,7 @@ Source code for minim.discogs
* :code:`"http.server"` for the built-in implementation
of HTTP servers.
* :code:`"flask"` for the Flask framework.
- * :code:`"playwright"` for the Playwright framework by
+ * :code:`"playwright"` for the Playwright framework by
Microsoft.
port : `int` or `str`, keyword-only, default: :code:`8888`
@@ -815,9 +817,9 @@ Source code for minim.discogs
if `redirect_uri` is not specified.
redirect_uri : `str`, keyword-only, optional
- Redirect URI for the OAuth 1.0a flow. If not on
- :code:`localhost`, the automatic request access token
- retrieval functionality is not available.
+ Redirect URI for the OAuth 1.0a flow. If not on
+ :code:`localhost`, the automatic request access token
+ retrieval functionality is not available.
save : `bool`, keyword-only, default: :code:`True`
Determines whether newly obtained access tokens and their
@@ -842,7 +844,7 @@ Source code for minim.discogs
if redirect_uri:
self._redirect_uri = redirect_uri
if "localhost" in redirect_uri:
- self._port = re.search(r"localhost:(\d+)",
+ self._port = re.search(r"localhost:(\d+)",
redirect_uri).group(1)
elif web_framework:
wmsg = ("The redirect URI is not on localhost, "
@@ -857,8 +859,8 @@ Source code for minim.discogs
self._port = self._redirect_uri = None
self._web_framework = (
- web_framework
- if web_framework is None
+ web_framework
+ if web_framework is None
or web_framework == "http.server"
or globals()[f"FOUND_{web_framework.upper()}"]
else None
@@ -896,8 +898,8 @@ Source code for minim.discogs
Currency abbreviation for marketplace data. Defaults to the
authenticated user's currency.
- **Valid values**: :code:`"USD"`, :code:`"GBP"`,
- :code:`"EUR"`, :code:`"CAD"`, :code:`"AUD"`, :code:`"JPY"`,
+ **Valid values**: :code:`"USD"`, :code:`"GBP"`,
+ :code:`"EUR"`, :code:`"CAD"`, :code:`"AUD"`, :code:`"JPY"`,
:code:`"CHF"`, :code:`"MXN"`, :code:`"BRL"`, :code:`"NZD"`,
:code:`"SEK"`, and :code:`"ZAR"`.
@@ -1038,7 +1040,7 @@ Source code for minim.discogs
"year": <int>
}
"""
-
+
if curr_abbr and curr_abbr not in (
CURRENCIES := {
"USD", "GBP", "EUR", "CAD", "AUD", "JPY",
@@ -1054,7 +1056,7 @@ Source code for minim.discogs
params={"curr_abbr": curr_abbr}
)
-
+
[docs]
def get_user_release_rating(
@@ -1076,7 +1078,7 @@ Source code for minim.discogs
username : `str`, optional
The username of the user whose rating you are requesting. If
- not specified, the username of the authenticated user is
+ not specified, the username of the authenticated user is
used.
**Example**: :code:`"memory"`.
@@ -1096,14 +1098,14 @@ Source code for minim.discogs
"release_id": <int>,
"rating": <int>
}
- """
-
+ """
+
if username is None:
if hasattr(self, "_username"):
username = self._username
else:
raise ValueError("No username provided.")
-
+
return self._get_json(
f"{self.API_URL}/releases/{release_id}/rating/{username}"
)
@@ -1112,9 +1114,9 @@ Source code for minim.discogs
[docs]
def update_user_release_rating(
- self, release_id: Union[int, str], rating: int,
+ self, release_id: Union[int, str], rating: int,
username: str = None) -> dict[str, Any]:
-
+
"""
`Database > Release Rating By User > Update Release Rating By
User <https://www.discogs.com/developers
@@ -1123,7 +1125,7 @@ Source code for minim.discogs
.. admonition:: User authentication
:class: warning
-
+
Requires user authentication with a personal access token or
via the OAuth 1.0a flow.
@@ -1139,7 +1141,7 @@ Source code for minim.discogs
username : `str`, optional
The username of the user whose rating you are requesting. If
- not specified, the username of the authenticated user is
+ not specified, the username of the authenticated user is
used.
**Example**: :code:`"memory"`.
@@ -1159,7 +1161,7 @@ Source code for minim.discogs
"release_id": <int>,
"rating": <int>
}
- """
+ """
self._check_authentication("update_user_release_rating")
@@ -1189,7 +1191,7 @@ Source code for minim.discogs
.. admonition:: User authentication
:class: warning
-
+
Requires user authentication with a personal access token or
via the OAuth 1.0a flow.
@@ -1202,20 +1204,20 @@ Source code for minim.discogs
username : `str`, optional
The username of the user whose rating you are requesting. If
- not specified, the username of the authenticated user is
+ not specified, the username of the authenticated user is
used.
**Example**: :code:`"memory"`.
"""
self._check_authentication("delete_user_release_rating")
-
+
if username is None:
if hasattr(self, "_username"):
username = self._username
else:
raise ValueError("No username provided.")
-
+
return self._request(
"delete",
f"{self.API_URL}/releases/{release_id}/rating/{username}"
@@ -1226,7 +1228,7 @@ Source code for minim.discogs
[docs]
def get_community_release_rating(
self, release_id: Union[int, str]) -> dict[str, Any]:
-
+
"""
`Database > Community Release Rating <https://www.discogs.com
/developers/#page:database,header
@@ -1258,7 +1260,7 @@ Source code for minim.discogs
"release_id": <int>
}
"""
-
+
return self._get_json(f"{self.API_URL}/releases/{release_id}/rating")
@@ -1273,7 +1275,7 @@ Source code for minim.discogs
.. attention::
- This endpoint does not appear to be working correctly.
+ This endpoint does not appear to be working correctly.
Currently, the response will be of the form
.. code::
@@ -1304,7 +1306,7 @@ Source code for minim.discogs
"num_want": <int>
}
"""
-
+
return self._get_json(f"{self.API_URL}/releases/{release_id}/stats")
@@ -1399,7 +1401,7 @@ Source code for minim.discogs
"data_quality": <str>
}
"""
-
+
return self._get_json(f"{self.API_URL}/masters/{master_id}")
@@ -1408,9 +1410,9 @@ Source code for minim.discogs
def get_master_release_versions(
self, master_id: Union[int, str], *, country: str = None,
format: str = None, label: str = None, released: str = None,
- page: int = None, per_page: int = None, sort: str = None,
- sort_order: str = None) -> dict[str, Any]:
-
+ page: Union[int, str] = None, per_page: Union[int, str] = None,
+ sort: str = None, sort_order: str = None) -> dict[str, Any]:
+
"""
`Database > Master Release Versions <https://www.discogs.com
/developers/#page:database,header
@@ -1444,12 +1446,12 @@ Source code for minim.discogs
**Example**: :code:`"1992"`.
- page : `int`, keyword-only, optional
+ page : `int` or `str`, keyword-only, optional
The page you want to request.
**Example**: :code:`3`.
- per_page : `int`, keyword-only, optional
+ per_page : `int` or `str`, keyword-only, optional
The number of items per page.
**Example**: :code:`25`.
@@ -1457,8 +1459,8 @@ Source code for minim.discogs
sort : `str`, keyword-only, optional
Sort items by this field.
- **Valid values**: :code:`"released"`, :code:`"title"`,
- :code:`"format"`, :code:`"label"`, :code:`"catno"`,
+ **Valid values**: :code:`"released"`, :code:`"title"`,
+ :code:`"format"`, :code:`"label"`, :code:`"catno"`,
and :code:`"country"`.
sort_order : `str`, keyword-only, optional
@@ -1469,7 +1471,7 @@ Source code for minim.discogs
Returns
-------
versions : `dict`
- Discogs database information for all releases that are
+ Discogs database information for all releases that are
versions of the specified master.
.. admonition:: Sample
@@ -1515,7 +1517,7 @@ Source code for minim.discogs
]
}
"""
-
+
return self._get_json(
f"{self.API_URL}/masters/{master_id}/versions",
params={
@@ -1530,7 +1532,7 @@ Source code for minim.discogs
},
)
-
+
[docs]
def get_artist(self, artist_id: Union[int, str]) -> dict[str, Any]:
@@ -1585,16 +1587,16 @@ Source code for minim.discogs
]
}
"""
-
+
return self._get_json(f"{self.API_URL}/artists/{artist_id}")
-
+
[docs]
def get_artist_releases(
- self, artist_id: Union[int, str], *, page: int = None,
- per_page: int = None, sort: str = None, sort_order: str = None
- ) -> dict[str, Any]:
+ self, artist_id: Union[int, str], *, page: Union[int, str] = None,
+ per_page: Union[int, str] = None, sort: str = None,
+ sort_order: str = None) -> dict[str, Any]:
"""
`Database > Artist Releases <https://www.discogs.com/developers
@@ -1608,10 +1610,10 @@ Source code for minim.discogs
**Example**: :code:`108713`.
- page : `int`, keyword-only, optional
+ page : `int` or `str`, keyword-only, optional
Page of results to fetch.
- per_page : `int`, keyword-only, optional
+ per_page : `int` or `str`, keyword-only, optional
Number of results per page.
sort : `str`, keyword-only, optional
@@ -1624,7 +1626,7 @@ Source code for minim.discogs
Sort results in a particular order.
**Valid values**: :code:`"asc"` and :code:`"desc"`.
-
+
Returns
-------
releases : `dict`
@@ -1662,7 +1664,7 @@ Source code for minim.discogs
]
}
"""
-
+
return self._get_json(
f"{self.API_URL}/artists/{artist_id}/releases",
params={
@@ -1677,10 +1679,10 @@ Source code for minim.discogs
[docs]
def get_label(self, label_id: Union[int, str]) -> dict[str, Any]:
-
+
"""
`Database > Label <https://www.discogs.com/developers
- /#page:database,header:database-label-get>`_: Get a label,
+ /#page:database,header:database-label-get>`_: Get a label,
company, recording studio, locxation, or other entity involved
with artists and releases.
@@ -1733,12 +1735,12 @@ Source code for minim.discogs
return self._get_json(f"{self.API_URL}/labels/{label_id}")
-
+
[docs]
def get_label_releases(
- self, label_id: Union[int, str], *, page: int = None,
- per_page: int = None) -> dict[str, Any]:
+ self, label_id: Union[int, str], *, page: Union[int, str] = None,
+ per_page: Union[int, str] = None) -> dict[str, Any]:
"""
`Database > Label Releases <https://www.discogs.com/developers
@@ -1752,10 +1754,10 @@ Source code for minim.discogs
**Example**: :code:`1`.
- page : `int`, keyword-only, optional
+ page : `int` or `str`, keyword-only, optional
Page of results to fetch.
- per_page : `int`, keyword-only, optional
+ per_page : `int` or `str`, keyword-only, optional
Number of results per page.
Returns
@@ -1795,13 +1797,13 @@ Source code for minim.discogs
]
}
"""
-
+
return self._get_json(
f"{self.API_URL}/labels/{label_id}/releases",
params={"page": page, "per_page": per_page}
)
-
+