yutipy 2.3.0__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of yutipy might be problematic. Click here for more details.
- {yutipy-2.3.0 → yutipy-2.3.1}/PKG-INFO +1 -1
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/api_reference.rst +3 -3
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/conf.py +1 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/base_clients.py +0 -5
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/deezer.py +15 -8
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/itunes.py +22 -10
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/kkbox.py +16 -10
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/lastfm.py +4 -8
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/lrclib.py +8 -10
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/musicyt.py +9 -3
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/spotify.py +27 -18
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/yutipy_music.py +21 -11
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy.egg-info/PKG-INFO +1 -1
- {yutipy-2.3.0 → yutipy-2.3.1}/.gitattributes +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.github/FUNDING.yml +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.github/dependabot.yml +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.github/workflows/pytest-unit-testing.yml +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.github/workflows/release.yml +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.gitignore +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/.readthedocs.yaml +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/LICENSE +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/MANIFEST.in +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/README.md +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/Makefile +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/_static/yutipy_header.png +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/_static/yutipy_logo.png +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/available_platforms.rst +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/cli.rst +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/faq.rst +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/index.rst +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/installation.rst +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/make.bat +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/requirements.txt +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/docs/usage_examples.rst +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/pyproject.toml +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/requirements-dev.txt +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/requirements.txt +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/setup.cfg +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/__init__.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_deezer.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_itunes.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_kkbox.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_lastfm.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_lrclib.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_models.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_musicyt.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_spotify.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/tests/test_utils.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/__init__.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/cli/__init__.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/cli/config.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/cli/search.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/exceptions.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/logger.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/models.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/utils/__init__.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy/utils/helpers.py +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy.egg-info/SOURCES.txt +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy.egg-info/dependency_links.txt +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy.egg-info/entry_points.txt +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy.egg-info/requires.txt +0 -0
- {yutipy-2.3.0 → yutipy-2.3.1}/yutipy.egg-info/top_level.txt +0 -0
|
@@ -30,7 +30,7 @@ KKBox
|
|
|
30
30
|
:members:
|
|
31
31
|
:inherited-members:
|
|
32
32
|
:noindex:
|
|
33
|
-
:exclude-members: is_session_closed
|
|
33
|
+
:exclude-members: is_session_closed, SERVICE_NAME, ACCESS_TOKEN_URL
|
|
34
34
|
|
|
35
35
|
Lastfm
|
|
36
36
|
------
|
|
@@ -57,13 +57,13 @@ Spotify
|
|
|
57
57
|
:members:
|
|
58
58
|
:inherited-members:
|
|
59
59
|
:noindex:
|
|
60
|
-
:exclude-members: is_session_closed
|
|
60
|
+
:exclude-members: is_session_closed, SERVICE_NAME, ACCESS_TOKEN_URL
|
|
61
61
|
|
|
62
62
|
.. autoclass:: yutipy.spotify.SpotifyAuth
|
|
63
63
|
:members:
|
|
64
64
|
:inherited-members:
|
|
65
65
|
:noindex:
|
|
66
|
-
:exclude-members: is_session_closed
|
|
66
|
+
:exclude-members: is_session_closed, SERVICE_NAME, ACCESS_TOKEN_URL, USER_AUTH_URL
|
|
67
67
|
|
|
68
68
|
YouTube Music
|
|
69
69
|
-------------
|
|
@@ -409,11 +409,6 @@ class BaseAuthClient:
|
|
|
409
409
|
|
|
410
410
|
def _refresh_access_token(self):
|
|
411
411
|
"""Refreshes the token if it has expired."""
|
|
412
|
-
try:
|
|
413
|
-
self.load_token_after_init()
|
|
414
|
-
except NotImplementedError as e:
|
|
415
|
-
logger.warning(e)
|
|
416
|
-
|
|
417
412
|
if not self._access_token or not self._refresh_token:
|
|
418
413
|
logger.warning(
|
|
419
414
|
"No access token or refresh token found. You must authenticate to obtain a new token."
|
|
@@ -15,11 +15,17 @@ from yutipy.lrclib import LrcLib
|
|
|
15
15
|
class Deezer:
|
|
16
16
|
"""A class to interact with the Deezer API."""
|
|
17
17
|
|
|
18
|
-
def __init__(self) -> None:
|
|
19
|
-
"""
|
|
18
|
+
def __init__(self, fetch_lyrics: bool = True) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
fetch_lyrics : bool, optional
|
|
23
|
+
Whether to fetch lyrics (using `LRCLIB <https://lrclib.net>`__) if the music platform does not provide lyrics (default is True).
|
|
24
|
+
"""
|
|
20
25
|
self.api_url = "https://api.deezer.com"
|
|
21
26
|
self._is_session_closed = False
|
|
22
27
|
self.normalize_non_english = True
|
|
28
|
+
self.fetch_lyrics = fetch_lyrics
|
|
23
29
|
self.__session = requests.Session()
|
|
24
30
|
self._translation_session = requests.Session()
|
|
25
31
|
|
|
@@ -304,12 +310,13 @@ class Deezer:
|
|
|
304
310
|
music_info.release_date = album_info.get("release_date")
|
|
305
311
|
music_info.genre = album_info.get("genre")
|
|
306
312
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
+
if self.fetch_lyrics:
|
|
314
|
+
with LrcLib() as lrc_lib:
|
|
315
|
+
lyrics = lrc_lib.get_lyrics(
|
|
316
|
+
artist=music_info.artists, song=music_info.title
|
|
317
|
+
)
|
|
318
|
+
if lyrics:
|
|
319
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
313
320
|
|
|
314
321
|
return music_info
|
|
315
322
|
|
|
@@ -8,19 +8,30 @@ import requests
|
|
|
8
8
|
|
|
9
9
|
from yutipy.exceptions import InvalidValueException, ItunesException
|
|
10
10
|
from yutipy.logger import logger
|
|
11
|
-
from yutipy.models import MusicInfo
|
|
12
|
-
from yutipy.utils.helpers import are_strings_similar, guess_album_type, is_valid_string, separate_artists
|
|
13
11
|
from yutipy.lrclib import LrcLib
|
|
12
|
+
from yutipy.models import MusicInfo
|
|
13
|
+
from yutipy.utils.helpers import (
|
|
14
|
+
are_strings_similar,
|
|
15
|
+
guess_album_type,
|
|
16
|
+
is_valid_string,
|
|
17
|
+
separate_artists,
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class Itunes:
|
|
17
22
|
"""A class to interact with the iTunes API."""
|
|
18
23
|
|
|
19
|
-
def __init__(self) -> None:
|
|
20
|
-
"""
|
|
24
|
+
def __init__(self, fetch_lyrics: bool = True) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
fetch_lyrics : bool, optional
|
|
29
|
+
Whether to fetch lyrics (using `LRCLIB <https://lrclib.net>`__) if the music platform does not provide lyrics (default is True).
|
|
30
|
+
"""
|
|
21
31
|
self.api_url = "https://itunes.apple.com"
|
|
22
32
|
self.normalize_non_english = True
|
|
23
33
|
self._is_session_closed = False
|
|
34
|
+
self.fetch_lyrics = fetch_lyrics
|
|
24
35
|
self.__session = requests.Session()
|
|
25
36
|
self.__translation_session = requests.Session()
|
|
26
37
|
|
|
@@ -169,12 +180,13 @@ class Itunes:
|
|
|
169
180
|
url=result.get("trackViewUrl", result["collectionViewUrl"]),
|
|
170
181
|
)
|
|
171
182
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
183
|
+
if self.fetch_lyrics:
|
|
184
|
+
with LrcLib() as lrc_lib:
|
|
185
|
+
lyrics = lrc_lib.get_lyrics(
|
|
186
|
+
artist=music_info.artists, song=music_info.title
|
|
187
|
+
)
|
|
188
|
+
if lyrics:
|
|
189
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
178
190
|
|
|
179
191
|
return music_info
|
|
180
192
|
return None
|
|
@@ -11,9 +11,9 @@ from dotenv import load_dotenv
|
|
|
11
11
|
from yutipy.base_clients import BaseClient
|
|
12
12
|
from yutipy.exceptions import InvalidValueException, KKBoxException
|
|
13
13
|
from yutipy.logger import logger
|
|
14
|
+
from yutipy.lrclib import LrcLib
|
|
14
15
|
from yutipy.models import MusicInfo
|
|
15
16
|
from yutipy.utils.helpers import are_strings_similar, is_valid_string
|
|
16
|
-
from yutipy.lrclib import LrcLib
|
|
17
17
|
|
|
18
18
|
load_dotenv()
|
|
19
19
|
|
|
@@ -30,11 +30,13 @@ class KKBox(BaseClient):
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
def __init__(
|
|
33
|
-
self,
|
|
33
|
+
self,
|
|
34
|
+
client_id: str = None,
|
|
35
|
+
client_secret: str = None,
|
|
36
|
+
defer_load: bool = False,
|
|
37
|
+
fetch_lyrics: bool = True,
|
|
34
38
|
) -> None:
|
|
35
39
|
"""
|
|
36
|
-
Initializes the KKBox class and sets up the session.
|
|
37
|
-
|
|
38
40
|
Parameters
|
|
39
41
|
----------
|
|
40
42
|
client_id : str, optional
|
|
@@ -43,9 +45,12 @@ class KKBox(BaseClient):
|
|
|
43
45
|
The Client secret for the KKBOX Open API. Defaults to ``KKBOX_CLIENT_SECRET`` from .env file.
|
|
44
46
|
defer_load : bool, optional
|
|
45
47
|
Whether to defer loading the access token during initialization. Default is ``False``.
|
|
48
|
+
fetch_lyrics : bool, optional
|
|
49
|
+
Whether to fetch lyrics (using `LRCLIB <https://lrclib.net>`__) if the music platform does not provide lyrics (default is True).
|
|
46
50
|
"""
|
|
47
51
|
self.client_id = client_id or KKBOX_CLIENT_ID
|
|
48
52
|
self.client_secret = client_secret or KKBOX_CLIENT_SECRET
|
|
53
|
+
self.fetch_lyrics = fetch_lyrics
|
|
49
54
|
|
|
50
55
|
if not self.client_id:
|
|
51
56
|
raise KKBoxException(
|
|
@@ -274,12 +279,13 @@ class KKBox(BaseClient):
|
|
|
274
279
|
url=track.get("url"),
|
|
275
280
|
)
|
|
276
281
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
if self.fetch_lyrics:
|
|
283
|
+
with LrcLib() as lrc_lib:
|
|
284
|
+
lyrics = lrc_lib.get_lyrics(
|
|
285
|
+
artist=music_info.artists, song=music_info.title
|
|
286
|
+
)
|
|
287
|
+
if lyrics:
|
|
288
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
283
289
|
return music_info
|
|
284
290
|
return None
|
|
285
291
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
__all__ = ["LastFm", "LastFmException"]
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from time import time
|
|
5
4
|
from pprint import pprint
|
|
5
|
+
from time import time
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
8
|
import requests
|
|
@@ -28,12 +28,6 @@ class LastFm:
|
|
|
28
28
|
|
|
29
29
|
def __init__(self, api_key: str = None):
|
|
30
30
|
"""
|
|
31
|
-
Initializes the LastFm class.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
lastfm_api_key (str, optional): The Last.fm API key. If not provided,
|
|
35
|
-
it will be fetched from the environment variable `LASTFM_API_KEY`.
|
|
36
|
-
|
|
37
31
|
Parameters
|
|
38
32
|
----------
|
|
39
33
|
lastfm_api_key : str, optional
|
|
@@ -141,7 +135,9 @@ class LastFm:
|
|
|
141
135
|
response_json = response.json()
|
|
142
136
|
result = response_json.get("recenttracks", {}).get("track", [])[0]
|
|
143
137
|
is_playing = result.get("@attr", {}).get("nowplaying", False)
|
|
144
|
-
is_playing =
|
|
138
|
+
is_playing = (
|
|
139
|
+
True if isinstance(is_playing, str) and is_playing == "true" else False
|
|
140
|
+
)
|
|
145
141
|
if result and is_playing:
|
|
146
142
|
album_art = [
|
|
147
143
|
img.get("#text")
|
|
@@ -21,8 +21,7 @@ class LrcLib:
|
|
|
21
21
|
app_version: str = None,
|
|
22
22
|
app_url: str = "https://github.com/CheapNightbot/yutipy",
|
|
23
23
|
) -> None:
|
|
24
|
-
"""
|
|
25
|
-
|
|
24
|
+
"""
|
|
26
25
|
Parameters
|
|
27
26
|
----------
|
|
28
27
|
app_name : str
|
|
@@ -135,14 +134,13 @@ class LrcLib:
|
|
|
135
134
|
use_translation=normalize_non_english,
|
|
136
135
|
translation_session=self._translation_session,
|
|
137
136
|
):
|
|
138
|
-
if album
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
continue
|
|
137
|
+
if album and not are_strings_similar(
|
|
138
|
+
result.get("albumName"),
|
|
139
|
+
album,
|
|
140
|
+
use_translation=normalize_non_english,
|
|
141
|
+
translation_session=self._translation_session,
|
|
142
|
+
):
|
|
143
|
+
continue
|
|
146
144
|
return result
|
|
147
145
|
return None
|
|
148
146
|
|
|
@@ -20,12 +20,18 @@ from yutipy.lrclib import LrcLib
|
|
|
20
20
|
class MusicYT:
|
|
21
21
|
"""A class to interact with the YouTube Music API."""
|
|
22
22
|
|
|
23
|
-
def __init__(self) -> None:
|
|
24
|
-
"""
|
|
23
|
+
def __init__(self, fetch_lyrics: bool = True) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
fetch_lyrics : bool, optional
|
|
28
|
+
Whether to fetch lyrics (using `LRCLIB <https://lrclib.net>`__) if the music platform does not provide lyrics (default is True).
|
|
29
|
+
"""
|
|
25
30
|
self.ytmusic = YTMusic()
|
|
26
31
|
self._is_session_closed = False
|
|
27
32
|
self.normalize_non_english = True
|
|
28
33
|
self.__translation_session = requests.Session()
|
|
34
|
+
self.fetch_lyrics = fetch_lyrics
|
|
29
35
|
|
|
30
36
|
def __enter__(self) -> "MusicYT":
|
|
31
37
|
"""Enters the runtime context related to this object."""
|
|
@@ -248,7 +254,7 @@ class MusicYT:
|
|
|
248
254
|
url=song_url,
|
|
249
255
|
)
|
|
250
256
|
|
|
251
|
-
if not music_info.lyrics:
|
|
257
|
+
if not music_info.lyrics and self.fetch_lyrics:
|
|
252
258
|
with LrcLib() as lrc_lib:
|
|
253
259
|
lyrics = lrc_lib.get_lyrics(
|
|
254
260
|
artist=music_info.artists, song=music_info.title
|
|
@@ -15,6 +15,7 @@ from yutipy.exceptions import (
|
|
|
15
15
|
SpotifyException,
|
|
16
16
|
)
|
|
17
17
|
from yutipy.logger import logger
|
|
18
|
+
from yutipy.lrclib import LrcLib
|
|
18
19
|
from yutipy.models import MusicInfo, UserPlaying
|
|
19
20
|
from yutipy.utils.helpers import (
|
|
20
21
|
are_strings_similar,
|
|
@@ -22,7 +23,6 @@ from yutipy.utils.helpers import (
|
|
|
22
23
|
is_valid_string,
|
|
23
24
|
separate_artists,
|
|
24
25
|
)
|
|
25
|
-
from yutipy.lrclib import LrcLib
|
|
26
26
|
|
|
27
27
|
load_dotenv()
|
|
28
28
|
|
|
@@ -40,11 +40,13 @@ class Spotify(BaseClient):
|
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
def __init__(
|
|
43
|
-
self,
|
|
43
|
+
self,
|
|
44
|
+
client_id: str = None,
|
|
45
|
+
client_secret: str = None,
|
|
46
|
+
defer_load: bool = False,
|
|
47
|
+
fetch_lyrics: bool = True,
|
|
44
48
|
) -> None:
|
|
45
49
|
"""
|
|
46
|
-
Initializes the Spotify class (using Client Credentials grant type/flow) and sets up the session.
|
|
47
|
-
|
|
48
50
|
Parameters
|
|
49
51
|
----------
|
|
50
52
|
client_id : str, optional
|
|
@@ -53,9 +55,12 @@ class Spotify(BaseClient):
|
|
|
53
55
|
The Client secret for the Spotify API. Defaults to ``SPOTIFY_CLIENT_SECRET`` from environment variable or the ``.env`` file.
|
|
54
56
|
defer_load : bool, optional
|
|
55
57
|
Whether to defer loading the access token during initialization, by default ``False``
|
|
58
|
+
fetch_lyrics : bool, optional
|
|
59
|
+
Whether to fetch lyrics (using `LRCLIB <https://lrclib.net>`__) if the music platform does not provide lyrics (default is True).
|
|
56
60
|
"""
|
|
57
61
|
self.client_id = client_id or SPOTIFY_CLIENT_ID
|
|
58
62
|
self.client_secret = client_secret or SPOTIFY_CLIENT_SECRET
|
|
63
|
+
self.fetch_lyrics = fetch_lyrics
|
|
59
64
|
|
|
60
65
|
if not self.client_id:
|
|
61
66
|
raise SpotifyException(
|
|
@@ -344,12 +349,13 @@ class Spotify(BaseClient):
|
|
|
344
349
|
url=track["external_urls"]["spotify"],
|
|
345
350
|
)
|
|
346
351
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
352
|
+
if self.fetch_lyrics:
|
|
353
|
+
with LrcLib() as lrc_lib:
|
|
354
|
+
lyrics = lrc_lib.get_lyrics(
|
|
355
|
+
artist=music_info.artists, song=music_info.title
|
|
356
|
+
)
|
|
357
|
+
if lyrics:
|
|
358
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
353
359
|
return music_info
|
|
354
360
|
return None
|
|
355
361
|
|
|
@@ -438,10 +444,9 @@ class SpotifyAuth(BaseAuthClient):
|
|
|
438
444
|
redirect_uri: str = None,
|
|
439
445
|
scopes: list[str] = None,
|
|
440
446
|
defer_load: bool = False,
|
|
447
|
+
fetch_lyrics: bool = True,
|
|
441
448
|
):
|
|
442
449
|
"""
|
|
443
|
-
Initializes the SpotifyAuth class (using Authorization Code grant type/flow) and sets up the session.
|
|
444
|
-
|
|
445
450
|
Parameters
|
|
446
451
|
----------
|
|
447
452
|
client_id : str, optional
|
|
@@ -454,11 +459,14 @@ class SpotifyAuth(BaseAuthClient):
|
|
|
454
459
|
A list of scopes for the Spotify API. For example: `['user-read-email', 'user-read-private']`.
|
|
455
460
|
defer_load : bool, optional
|
|
456
461
|
Whether to defer loading the access token during initialization. Default is ``False``.
|
|
462
|
+
fetch_lyrics : bool, optional
|
|
463
|
+
Whether to fetch lyrics using `LRCLIB <https://lrclib.net>`__ if the music platform does not provide lyrics (default is True).
|
|
457
464
|
"""
|
|
458
465
|
self.client_id = client_id or os.getenv("SPOTIFY_CLIENT_ID")
|
|
459
466
|
self.client_secret = client_secret or os.getenv("SPOTIFY_CLIENT_SECRET")
|
|
460
467
|
self.redirect_uri = redirect_uri or os.getenv("SPOTIFY_REDIRECT_URI")
|
|
461
468
|
self.scopes = scopes
|
|
469
|
+
self.fetch_lyrics = fetch_lyrics
|
|
462
470
|
|
|
463
471
|
if not self.client_id:
|
|
464
472
|
raise SpotifyAuthException(
|
|
@@ -600,12 +608,13 @@ class SpotifyAuth(BaseAuthClient):
|
|
|
600
608
|
url=result.get("external_urls", {}).get("spotify"),
|
|
601
609
|
)
|
|
602
610
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
611
|
+
if self.fetch_lyrics:
|
|
612
|
+
with LrcLib() as lrc_lib:
|
|
613
|
+
lyrics = lrc_lib.get_lyrics(
|
|
614
|
+
artist=user_playing.artists, song=user_playing.title
|
|
615
|
+
)
|
|
616
|
+
if lyrics:
|
|
617
|
+
user_playing.lyrics = lyrics.get("plainLyrics")
|
|
609
618
|
return user_playing
|
|
610
619
|
return None
|
|
611
620
|
|
|
@@ -8,11 +8,12 @@ from yutipy.deezer import Deezer
|
|
|
8
8
|
from yutipy.exceptions import InvalidValueException, KKBoxException, SpotifyException
|
|
9
9
|
from yutipy.itunes import Itunes
|
|
10
10
|
from yutipy.kkbox import KKBox
|
|
11
|
+
from yutipy.logger import logger
|
|
12
|
+
from yutipy.lrclib import LrcLib
|
|
11
13
|
from yutipy.models import MusicInfo, MusicInfos
|
|
12
14
|
from yutipy.musicyt import MusicYT
|
|
13
15
|
from yutipy.spotify import Spotify
|
|
14
16
|
from yutipy.utils.helpers import is_valid_string
|
|
15
|
-
from yutipy.logger import logger
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class YutipyMusic:
|
|
@@ -28,8 +29,6 @@ class YutipyMusic:
|
|
|
28
29
|
custom_spotify_class=Spotify,
|
|
29
30
|
) -> None:
|
|
30
31
|
"""
|
|
31
|
-
Initializes the YutipyMusic class.
|
|
32
|
-
|
|
33
32
|
Parameters
|
|
34
33
|
----------
|
|
35
34
|
custom_kkbox_class : Optional[type], optional
|
|
@@ -43,13 +42,15 @@ class YutipyMusic:
|
|
|
43
42
|
self.normalize_non_english = True
|
|
44
43
|
self.album_art_priority = ["deezer", "ytmusic", "itunes"]
|
|
45
44
|
self.services = {
|
|
46
|
-
"deezer": Deezer(),
|
|
47
|
-
"itunes": Itunes(),
|
|
48
|
-
"ytmusic": MusicYT(),
|
|
45
|
+
"deezer": Deezer(fetch_lyrics=False),
|
|
46
|
+
"itunes": Itunes(fetch_lyrics=False),
|
|
47
|
+
"ytmusic": MusicYT(fetch_lyrics=False),
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
try:
|
|
52
|
-
self.services["kkbox"] = custom_kkbox_class(
|
|
51
|
+
self.services["kkbox"] = custom_kkbox_class(
|
|
52
|
+
defer_load=True, fetch_lyrics=False
|
|
53
|
+
)
|
|
53
54
|
except KKBoxException as e:
|
|
54
55
|
logger.warning(
|
|
55
56
|
f"{self.__class__.__name__}: Skipping KKBox due to KKBoxException: {e}"
|
|
@@ -59,7 +60,9 @@ class YutipyMusic:
|
|
|
59
60
|
self.album_art_priority.insert(idx, "kkbox")
|
|
60
61
|
|
|
61
62
|
try:
|
|
62
|
-
self.services["spotify"] = custom_spotify_class(
|
|
63
|
+
self.services["spotify"] = custom_spotify_class(
|
|
64
|
+
defer_load=True, fetch_lyrics=False
|
|
65
|
+
)
|
|
63
66
|
except SpotifyException as e:
|
|
64
67
|
logger.warning(
|
|
65
68
|
f"{self.__class__.__name__}: Skipping Spotify due to SpotifyException: {e}"
|
|
@@ -134,11 +137,18 @@ class YutipyMusic:
|
|
|
134
137
|
)
|
|
135
138
|
|
|
136
139
|
if len(self.music_info.url) == 0:
|
|
137
|
-
logger.
|
|
140
|
+
logger.info(
|
|
138
141
|
f"No matching results found across all platforms for artist='{artist}' and song='{song}'"
|
|
139
142
|
)
|
|
140
143
|
return None
|
|
141
144
|
|
|
145
|
+
# Fetch lyrics only once using LrcLib if not already present
|
|
146
|
+
if not self.music_info.lyrics:
|
|
147
|
+
with LrcLib() as lrc_lib:
|
|
148
|
+
lyrics_result = lrc_lib.get_lyrics(artist, song)
|
|
149
|
+
if lyrics_result:
|
|
150
|
+
self.music_info.lyrics = lyrics_result.get("plainLyrics")
|
|
151
|
+
|
|
142
152
|
return self.music_info
|
|
143
153
|
|
|
144
154
|
def _combine_results(self, result: Optional[MusicInfo], service_name: str) -> None:
|
|
@@ -199,10 +209,10 @@ class YutipyMusic:
|
|
|
199
209
|
|
|
200
210
|
if __name__ == "__main__":
|
|
201
211
|
import logging
|
|
202
|
-
from yutipy.logger import enable_logging
|
|
203
|
-
|
|
204
212
|
from dataclasses import asdict
|
|
205
213
|
|
|
214
|
+
from yutipy.logger import enable_logging
|
|
215
|
+
|
|
206
216
|
enable_logging(level=logging.DEBUG)
|
|
207
217
|
yutipy_music = YutipyMusic()
|
|
208
218
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|