yutipy 2.2.15__py3-none-any.whl → 2.3.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.
Potentially problematic release.
This version of yutipy might be problematic. Click here for more details.
- yutipy/__init__.py +2 -0
- yutipy/base_clients.py +16 -0
- yutipy/deezer.py +10 -2
- yutipy/itunes.py +11 -2
- yutipy/kkbox.py +9 -1
- yutipy/lrclib.py +168 -0
- yutipy/musicyt.py +15 -3
- yutipy/spotify.py +19 -6
- yutipy/yutipy_music.py +7 -5
- {yutipy-2.2.15.dist-info → yutipy-2.3.0.dist-info}/METADATA +4 -1
- yutipy-2.3.0.dist-info/RECORD +24 -0
- yutipy-2.2.15.dist-info/RECORD +0 -23
- {yutipy-2.2.15.dist-info → yutipy-2.3.0.dist-info}/WHEEL +0 -0
- {yutipy-2.2.15.dist-info → yutipy-2.3.0.dist-info}/entry_points.txt +0 -0
- {yutipy-2.2.15.dist-info → yutipy-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {yutipy-2.2.15.dist-info → yutipy-2.3.0.dist-info}/top_level.txt +0 -0
yutipy/__init__.py
CHANGED
yutipy/base_clients.py
CHANGED
|
@@ -384,6 +384,11 @@ class BaseAuthClient:
|
|
|
384
384
|
"refresh_token": self._refresh_token,
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
if not data:
|
|
388
|
+
raise AuthenticationException(
|
|
389
|
+
"Either `authorization_code` or `refresh_token` must be provided to get access token."
|
|
390
|
+
)
|
|
391
|
+
|
|
387
392
|
try:
|
|
388
393
|
logger.info(
|
|
389
394
|
f"Authenticating with {self.SERVICE_NAME} API using Authorization Code grant type."
|
|
@@ -404,6 +409,17 @@ class BaseAuthClient:
|
|
|
404
409
|
|
|
405
410
|
def _refresh_access_token(self):
|
|
406
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
|
+
if not self._access_token or not self._refresh_token:
|
|
418
|
+
logger.warning(
|
|
419
|
+
"No access token or refresh token found. You must authenticate to obtain a new token."
|
|
420
|
+
)
|
|
421
|
+
return
|
|
422
|
+
|
|
407
423
|
try:
|
|
408
424
|
if time() - self._token_requested_at >= self._token_expires_in:
|
|
409
425
|
token_info = self._get_access_token(refresh_token=self._refresh_token)
|
yutipy/deezer.py
CHANGED
|
@@ -9,6 +9,7 @@ from yutipy.exceptions import DeezerException, InvalidValueException
|
|
|
9
9
|
from yutipy.logger import logger
|
|
10
10
|
from yutipy.models import MusicInfo
|
|
11
11
|
from yutipy.utils.helpers import are_strings_similar, is_valid_string
|
|
12
|
+
from yutipy.lrclib import LrcLib
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class Deezer:
|
|
@@ -95,7 +96,7 @@ class Deezer:
|
|
|
95
96
|
return None
|
|
96
97
|
|
|
97
98
|
try:
|
|
98
|
-
logger.debug(
|
|
99
|
+
logger.debug("Parsing response JSON.")
|
|
99
100
|
result = response.json()["data"]
|
|
100
101
|
except (IndexError, KeyError, ValueError) as e:
|
|
101
102
|
logger.warning(f"Invalid response structure from Deezer: {e}")
|
|
@@ -159,7 +160,7 @@ class Deezer:
|
|
|
159
160
|
return None
|
|
160
161
|
|
|
161
162
|
try:
|
|
162
|
-
logger.debug(
|
|
163
|
+
logger.debug("Parsing Response JSON.")
|
|
163
164
|
result = response.json()
|
|
164
165
|
except ValueError as e:
|
|
165
166
|
logger.warning(f"Invalid response received from Deezer: {e}")
|
|
@@ -303,6 +304,13 @@ class Deezer:
|
|
|
303
304
|
music_info.release_date = album_info.get("release_date")
|
|
304
305
|
music_info.genre = album_info.get("genre")
|
|
305
306
|
|
|
307
|
+
with LrcLib() as lrc_lib:
|
|
308
|
+
lyrics = lrc_lib.get_lyrics(
|
|
309
|
+
artist=music_info.artists, song=music_info.title
|
|
310
|
+
)
|
|
311
|
+
if lyrics:
|
|
312
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
313
|
+
|
|
306
314
|
return music_info
|
|
307
315
|
|
|
308
316
|
|
yutipy/itunes.py
CHANGED
|
@@ -10,6 +10,7 @@ from yutipy.exceptions import InvalidValueException, ItunesException
|
|
|
10
10
|
from yutipy.logger import logger
|
|
11
11
|
from yutipy.models import MusicInfo
|
|
12
12
|
from yutipy.utils.helpers import are_strings_similar, guess_album_type, is_valid_string, separate_artists
|
|
13
|
+
from yutipy.lrclib import LrcLib
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class Itunes:
|
|
@@ -95,7 +96,7 @@ class Itunes:
|
|
|
95
96
|
return None
|
|
96
97
|
|
|
97
98
|
try:
|
|
98
|
-
logger.debug(
|
|
99
|
+
logger.debug("Parsing response JSON.")
|
|
99
100
|
result = response.json()["results"]
|
|
100
101
|
except (IndexError, KeyError, ValueError) as e:
|
|
101
102
|
logger.warning(f"Invalid response structure from iTunes: {e}")
|
|
@@ -151,7 +152,7 @@ class Itunes:
|
|
|
151
152
|
release_date = self._format_release_date(result["releaseDate"])
|
|
152
153
|
artists = separate_artists(result["artistName"])
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
music_info = MusicInfo(
|
|
155
156
|
album_art=result["artworkUrl100"],
|
|
156
157
|
album_title=album_title,
|
|
157
158
|
album_type=album_type.lower(),
|
|
@@ -168,6 +169,14 @@ class Itunes:
|
|
|
168
169
|
url=result.get("trackViewUrl", result["collectionViewUrl"]),
|
|
169
170
|
)
|
|
170
171
|
|
|
172
|
+
with LrcLib() as lrc_lib:
|
|
173
|
+
lyrics = lrc_lib.get_lyrics(
|
|
174
|
+
artist=music_info.artists, song=music_info.title
|
|
175
|
+
)
|
|
176
|
+
if lyrics:
|
|
177
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
178
|
+
|
|
179
|
+
return music_info
|
|
171
180
|
return None
|
|
172
181
|
|
|
173
182
|
def _extract_album_info(self, result: dict) -> tuple:
|
yutipy/kkbox.py
CHANGED
|
@@ -13,6 +13,7 @@ from yutipy.exceptions import InvalidValueException, KKBoxException
|
|
|
13
13
|
from yutipy.logger import logger
|
|
14
14
|
from yutipy.models import MusicInfo
|
|
15
15
|
from yutipy.utils.helpers import are_strings_similar, is_valid_string
|
|
16
|
+
from yutipy.lrclib import LrcLib
|
|
16
17
|
|
|
17
18
|
load_dotenv()
|
|
18
19
|
|
|
@@ -256,7 +257,7 @@ class KKBox(BaseClient):
|
|
|
256
257
|
)
|
|
257
258
|
|
|
258
259
|
if matching_artists:
|
|
259
|
-
|
|
260
|
+
music_info = MusicInfo(
|
|
260
261
|
album_art=track.get("album", {}).get("images", [])[2]["url"],
|
|
261
262
|
album_title=track.get("album", {}).get("name"),
|
|
262
263
|
album_type=None,
|
|
@@ -273,6 +274,13 @@ class KKBox(BaseClient):
|
|
|
273
274
|
url=track.get("url"),
|
|
274
275
|
)
|
|
275
276
|
|
|
277
|
+
with LrcLib() as lrc_lib:
|
|
278
|
+
lyrics = lrc_lib.get_lyrics(
|
|
279
|
+
artist=music_info.artists, song=music_info.title
|
|
280
|
+
)
|
|
281
|
+
if lyrics:
|
|
282
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
283
|
+
return music_info
|
|
276
284
|
return None
|
|
277
285
|
|
|
278
286
|
def _find_album(self, song: str, artist: str, album: dict) -> Optional[MusicInfo]:
|
yutipy/lrclib.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
__all__ = ["LrcLib"]
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from yutipy.exceptions import InvalidValueException
|
|
9
|
+
from yutipy.logger import logger
|
|
10
|
+
from yutipy.utils import are_strings_similar, is_valid_string
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LrcLib:
|
|
14
|
+
"""
|
|
15
|
+
A class to interact with the `LRCLIB <lrclib.net>`_ API for fetching lyrics.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
app_name: str = "yutipy",
|
|
21
|
+
app_version: str = None,
|
|
22
|
+
app_url: str = "https://github.com/CheapNightbot/yutipy",
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Initializes the LrcLib with the API URL and application details.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
app_name : str
|
|
29
|
+
The name of the application.
|
|
30
|
+
app_version : str, optional
|
|
31
|
+
The version of the application.
|
|
32
|
+
app_url : str, optional
|
|
33
|
+
The URL of the application.
|
|
34
|
+
|
|
35
|
+
Notes
|
|
36
|
+
-----
|
|
37
|
+
These are used to set the User-Agent header for requests made to the API as suggested by the API documentation of `LRCLIB <lrclib.net>`_.
|
|
38
|
+
"""
|
|
39
|
+
self.api_url = "https://lrclib.net/api"
|
|
40
|
+
self.app_name = app_name
|
|
41
|
+
self.app_url = app_url
|
|
42
|
+
if not app_version:
|
|
43
|
+
try:
|
|
44
|
+
self.app_version = f"v{version('yutipy')}"
|
|
45
|
+
except PackageNotFoundError:
|
|
46
|
+
self.app_version = "N/A"
|
|
47
|
+
else:
|
|
48
|
+
self.app_version = app_version
|
|
49
|
+
|
|
50
|
+
self._is_session_closed = False
|
|
51
|
+
self.__session = requests.Session()
|
|
52
|
+
self.__session.headers.update(
|
|
53
|
+
{"User-Agent": f"{self.app_name} {self.app_version} ({self.app_url})"}
|
|
54
|
+
)
|
|
55
|
+
self._translation_session = requests.Session()
|
|
56
|
+
|
|
57
|
+
def __enter__(self):
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
61
|
+
self.close_session()
|
|
62
|
+
|
|
63
|
+
def close_session(self):
|
|
64
|
+
"""
|
|
65
|
+
Closes the session if it is not already closed.
|
|
66
|
+
"""
|
|
67
|
+
if not self._is_session_closed:
|
|
68
|
+
self.__session.close()
|
|
69
|
+
self._translation_session.close()
|
|
70
|
+
self._is_session_closed = True
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_session_closed(self) -> bool:
|
|
74
|
+
return self._is_session_closed
|
|
75
|
+
|
|
76
|
+
def get_lyrics(
|
|
77
|
+
self,
|
|
78
|
+
artist: str,
|
|
79
|
+
song: str,
|
|
80
|
+
album: str = None,
|
|
81
|
+
normalize_non_english: bool = True,
|
|
82
|
+
) -> Optional[dict]:
|
|
83
|
+
"""
|
|
84
|
+
Fetches lyrics for a given artist and song.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
artist : str
|
|
89
|
+
The name of the artist.
|
|
90
|
+
song : str
|
|
91
|
+
The title of the song.
|
|
92
|
+
album : str, optional
|
|
93
|
+
The title of the album.
|
|
94
|
+
normalize_non_english : bool, optional
|
|
95
|
+
Whether to normalize non-English characters for comparison (default is True).
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
Optional[dict]
|
|
100
|
+
The lyrics information if found, otherwise None.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
if not is_valid_string(artist) or not is_valid_string(song):
|
|
104
|
+
raise InvalidValueException(
|
|
105
|
+
"Artist and song names must be valid strings and can't be empty."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
endpoint = f"{self.api_url}/search"
|
|
109
|
+
query = f"?artist_name={artist}&track_name={song}"
|
|
110
|
+
query += f"&album_name={album}" if album else ""
|
|
111
|
+
query_url = endpoint + query
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
logger.info(
|
|
115
|
+
f"Fetching lyrics for artist: {artist}, song: {song}, album: {album}"
|
|
116
|
+
)
|
|
117
|
+
response = self.__session.get(query_url, timeout=30)
|
|
118
|
+
logger.debug(f"Response status code: {response.status_code}")
|
|
119
|
+
response.raise_for_status()
|
|
120
|
+
except requests.RequestException as e:
|
|
121
|
+
logger.warning(f"Unexpected error while fetching lyrics: {e}")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
results = response.json()
|
|
125
|
+
|
|
126
|
+
for result in results:
|
|
127
|
+
if are_strings_similar(
|
|
128
|
+
result.get("trackName"),
|
|
129
|
+
song,
|
|
130
|
+
use_translation=normalize_non_english,
|
|
131
|
+
translation_session=self._translation_session,
|
|
132
|
+
) and are_strings_similar(
|
|
133
|
+
result.get("artistName"),
|
|
134
|
+
artist,
|
|
135
|
+
use_translation=normalize_non_english,
|
|
136
|
+
translation_session=self._translation_session,
|
|
137
|
+
):
|
|
138
|
+
if album:
|
|
139
|
+
if not are_strings_similar(
|
|
140
|
+
result.get("albumName"),
|
|
141
|
+
album,
|
|
142
|
+
use_translation=normalize_non_english,
|
|
143
|
+
translation_session=self._translation_session,
|
|
144
|
+
):
|
|
145
|
+
continue
|
|
146
|
+
return result
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
import logging
|
|
152
|
+
|
|
153
|
+
from yutipy.logger import enable_logging
|
|
154
|
+
|
|
155
|
+
enable_logging(level=logging.DEBUG)
|
|
156
|
+
|
|
157
|
+
with LrcLib() as lyric_lib:
|
|
158
|
+
artist_name = input("Artist Name: ").strip()
|
|
159
|
+
song_title = input("Song Title: ").strip()
|
|
160
|
+
lyrics = lyric_lib.get_lyrics(artist_name, song_title)
|
|
161
|
+
print(f"\nLyrics for '{song_title}' by {artist_name}:\n{'-' * 40}\n")
|
|
162
|
+
|
|
163
|
+
if lyrics:
|
|
164
|
+
print(lyrics.get("plainLyrics"))
|
|
165
|
+
else:
|
|
166
|
+
print(
|
|
167
|
+
"It seems that the lyrics were not found! You might have to guess them..."
|
|
168
|
+
)
|
yutipy/musicyt.py
CHANGED
|
@@ -14,6 +14,7 @@ from yutipy.exceptions import (
|
|
|
14
14
|
from yutipy.logger import logger
|
|
15
15
|
from yutipy.models import MusicInfo
|
|
16
16
|
from yutipy.utils.helpers import are_strings_similar, is_valid_string
|
|
17
|
+
from yutipy.lrclib import LrcLib
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class MusicYT:
|
|
@@ -224,12 +225,13 @@ class MusicYT:
|
|
|
224
225
|
|
|
225
226
|
try:
|
|
226
227
|
lyrics = self.ytmusic.get_lyrics(lyrics_id.get("lyrics"))
|
|
228
|
+
lyrics = lyrics.get("lyrics")
|
|
227
229
|
except exceptions.YTMusicUserError:
|
|
228
|
-
lyrics =
|
|
230
|
+
lyrics = None
|
|
229
231
|
|
|
230
232
|
album_art = result.get("thumbnails", [{}])[-1].get("url", None)
|
|
231
233
|
|
|
232
|
-
|
|
234
|
+
music_info = MusicInfo(
|
|
233
235
|
album_art=album_art,
|
|
234
236
|
album_title=None,
|
|
235
237
|
album_type="single",
|
|
@@ -237,7 +239,7 @@ class MusicYT:
|
|
|
237
239
|
genre=None,
|
|
238
240
|
id=video_id,
|
|
239
241
|
isrc=None,
|
|
240
|
-
lyrics=lyrics
|
|
242
|
+
lyrics=lyrics,
|
|
241
243
|
release_date=release_date,
|
|
242
244
|
tempo=None,
|
|
243
245
|
title=title,
|
|
@@ -246,6 +248,16 @@ class MusicYT:
|
|
|
246
248
|
url=song_url,
|
|
247
249
|
)
|
|
248
250
|
|
|
251
|
+
if not music_info.lyrics:
|
|
252
|
+
with LrcLib() as lrc_lib:
|
|
253
|
+
lyrics = lrc_lib.get_lyrics(
|
|
254
|
+
artist=music_info.artists, song=music_info.title
|
|
255
|
+
)
|
|
256
|
+
if lyrics:
|
|
257
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
258
|
+
|
|
259
|
+
return music_info
|
|
260
|
+
|
|
249
261
|
def _get_album(self, result: dict) -> MusicInfo:
|
|
250
262
|
"""
|
|
251
263
|
Return album info as a `MusicInfo` object.
|
yutipy/spotify.py
CHANGED
|
@@ -22,6 +22,7 @@ from yutipy.utils.helpers import (
|
|
|
22
22
|
is_valid_string,
|
|
23
23
|
separate_artists,
|
|
24
24
|
)
|
|
25
|
+
from yutipy.lrclib import LrcLib
|
|
25
26
|
|
|
26
27
|
load_dotenv()
|
|
27
28
|
|
|
@@ -135,7 +136,7 @@ class Spotify(BaseClient):
|
|
|
135
136
|
)
|
|
136
137
|
response.raise_for_status()
|
|
137
138
|
except requests.RequestException as e:
|
|
138
|
-
logger.warning(f"Failed to search for music: {
|
|
139
|
+
logger.warning(f"Failed to search for music: {e}")
|
|
139
140
|
return None
|
|
140
141
|
|
|
141
142
|
artist_ids = artist_ids if artist_ids else self._get_artists_ids(artist)
|
|
@@ -199,9 +200,7 @@ class Spotify(BaseClient):
|
|
|
199
200
|
)
|
|
200
201
|
response.raise_for_status()
|
|
201
202
|
except requests.RequestException as e:
|
|
202
|
-
raise logger.warning(
|
|
203
|
-
f"Failed to search music with ISRC/UPC: {response.json()}"
|
|
204
|
-
)
|
|
203
|
+
raise logger.warning(f"Failed to search music with ISRC/UPC: {e}")
|
|
205
204
|
return None
|
|
206
205
|
|
|
207
206
|
artist_ids = self._get_artists_ids(artist)
|
|
@@ -328,7 +327,7 @@ class Spotify(BaseClient):
|
|
|
328
327
|
]
|
|
329
328
|
|
|
330
329
|
if matching_artists:
|
|
331
|
-
|
|
330
|
+
music_info = MusicInfo(
|
|
332
331
|
album_art=track["album"]["images"][0]["url"],
|
|
333
332
|
album_title=track["album"]["name"],
|
|
334
333
|
album_type=track["album"]["album_type"],
|
|
@@ -345,6 +344,13 @@ class Spotify(BaseClient):
|
|
|
345
344
|
url=track["external_urls"]["spotify"],
|
|
346
345
|
)
|
|
347
346
|
|
|
347
|
+
with LrcLib() as lrc_lib:
|
|
348
|
+
lyrics = lrc_lib.get_lyrics(
|
|
349
|
+
artist=music_info.artists, song=music_info.title
|
|
350
|
+
)
|
|
351
|
+
if lyrics:
|
|
352
|
+
music_info.lyrics = lyrics.get("plainLyrics")
|
|
353
|
+
return music_info
|
|
348
354
|
return None
|
|
349
355
|
|
|
350
356
|
def _find_album(
|
|
@@ -571,7 +577,7 @@ class SpotifyAuth(BaseAuthClient):
|
|
|
571
577
|
)
|
|
572
578
|
# Spotify returns timestamp in milliseconds, so convert milliseconds to seconds:
|
|
573
579
|
timestamp = response_json.get("timestamp") / 1000.0
|
|
574
|
-
|
|
580
|
+
user_playing = UserPlaying(
|
|
575
581
|
album_art=result.get("album", {}).get("images", [])[0].get("url"),
|
|
576
582
|
album_title=result.get("album", {}).get("name"),
|
|
577
583
|
album_type=(
|
|
@@ -594,6 +600,13 @@ class SpotifyAuth(BaseAuthClient):
|
|
|
594
600
|
url=result.get("external_urls", {}).get("spotify"),
|
|
595
601
|
)
|
|
596
602
|
|
|
603
|
+
with LrcLib() as lrc_lib:
|
|
604
|
+
lyrics = lrc_lib.get_lyrics(
|
|
605
|
+
artist=user_playing.artists, song=user_playing.title
|
|
606
|
+
)
|
|
607
|
+
if lyrics:
|
|
608
|
+
user_playing.lyrics = lyrics.get("plainLyrics")
|
|
609
|
+
return user_playing
|
|
597
610
|
return None
|
|
598
611
|
|
|
599
612
|
|
yutipy/yutipy_music.py
CHANGED
|
@@ -23,10 +23,10 @@ class YutipyMusic:
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
def __init__(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
) -> None:
|
|
26
|
+
self,
|
|
27
|
+
custom_kkbox_class=KKBox,
|
|
28
|
+
custom_spotify_class=Spotify,
|
|
29
|
+
) -> None:
|
|
30
30
|
"""
|
|
31
31
|
Initializes the YutipyMusic class.
|
|
32
32
|
|
|
@@ -201,11 +201,13 @@ if __name__ == "__main__":
|
|
|
201
201
|
import logging
|
|
202
202
|
from yutipy.logger import enable_logging
|
|
203
203
|
|
|
204
|
+
from dataclasses import asdict
|
|
205
|
+
|
|
204
206
|
enable_logging(level=logging.DEBUG)
|
|
205
207
|
yutipy_music = YutipyMusic()
|
|
206
208
|
|
|
207
209
|
artist_name = input("Artist Name: ")
|
|
208
210
|
song_name = input("Song Name: ")
|
|
209
211
|
|
|
210
|
-
pprint(yutipy_music.search(artist_name, song_name))
|
|
212
|
+
pprint(asdict(yutipy_music.search(artist_name, song_name)))
|
|
211
213
|
yutipy_music.close_sessions()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yutipy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A simple Python package to interact with various music platforms APIs.
|
|
5
5
|
Author: Cheap Nightbot
|
|
6
6
|
Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
@@ -57,6 +57,9 @@ Dynamic: license-file
|
|
|
57
57
|
</a>
|
|
58
58
|
</p>
|
|
59
59
|
|
|
60
|
+
> **Looking for an easy-to-use API or GUI to search for music, instead of using the CLI or building your own integration?**
|
|
61
|
+
> Check out [yutify](https://yutify.cheapnightbot.me) — it’s powered by yutipy!
|
|
62
|
+
|
|
60
63
|
A _**simple**_ Python package to interact with various music platforms APIs.
|
|
61
64
|
|
|
62
65
|
## Table of Contents
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
yutipy/__init__.py,sha256=DjHnnzikF6QIvCVqpelYUGQalYjovs6hSf6vbfPnEqM,320
|
|
2
|
+
yutipy/base_clients.py,sha256=CLRCB69ZMdviluCfTU10bcwG8N3N1adpgWdYcSQonY4,22222
|
|
3
|
+
yutipy/deezer.py,sha256=SYzC_XDOwub-pCtF_j_cgTveeHynAg71GtDwnRjQw6I,10726
|
|
4
|
+
yutipy/exceptions.py,sha256=zz0XyyZr5xRcmRyw3hdTGaVRcwRn_RSYZdmwmuO0sEM,1379
|
|
5
|
+
yutipy/itunes.py,sha256=vVPK-KeOVXLMI69lRxUN12iUpfKjDINLkXlH21UsvIs,7718
|
|
6
|
+
yutipy/kkbox.py,sha256=IBEEkKh5nkmPSXB3sUnV1BWz53BBJWJUMvyu-o2knBw,11827
|
|
7
|
+
yutipy/lastfm.py,sha256=hOFQOZdf51Gp2m02b4NjKRmQ9yQZ9Yas6MaM78c-oCg,5970
|
|
8
|
+
yutipy/logger.py,sha256=GyLBlfQZ6pLNJ5MbyQSvcD_PkxmFdX41DPq5aeG1z68,1316
|
|
9
|
+
yutipy/lrclib.py,sha256=RxB7g4L-4cikg8Wpjlj2UyC4L_Qhwb_vO_Et0XpqoOk,5329
|
|
10
|
+
yutipy/models.py,sha256=45M-bNHusaAan_Ta_E9DyvsWujsT-ivbJqIfy2-i3R8,2343
|
|
11
|
+
yutipy/musicyt.py,sha256=ApTddFWBMX6gnSY-WbEGHrEMk3AsCfNa7AtbVUuj1z0,9858
|
|
12
|
+
yutipy/spotify.py,sha256=o4vSSrMzTK1Oe800vhFh1RLoylXOB-0DGV5QnhH5feM,23183
|
|
13
|
+
yutipy/yutipy_music.py,sha256=uIN1zeu0aJGEOcQcfFQ88I2kkhFgK8gzrGmWeFPTWNY,7328
|
|
14
|
+
yutipy/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
yutipy/cli/config.py,sha256=e5RIq6RxVxxzx30nKVMa06gwyQ258s7U0WA1xvJuR_0,4543
|
|
16
|
+
yutipy/cli/search.py,sha256=8SQw0bjRzRqAg-FuVz9aWjB2KBZqmCf38SyKAQ3rx5E,3025
|
|
17
|
+
yutipy/utils/__init__.py,sha256=AZaqvs6AJwnqwJuodbGnHu702WSUqc8plVC16SppOcU,239
|
|
18
|
+
yutipy/utils/helpers.py,sha256=-iH0bx_sxW3Y3jjl6eTbY6QOBoG5t4obRcp7GGyw3ro,7476
|
|
19
|
+
yutipy-2.3.0.dist-info/licenses/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
|
|
20
|
+
yutipy-2.3.0.dist-info/METADATA,sha256=VLzLHljOSZ-XF61eVmUFyQml_DHNdy20WNJEVYV1BL8,6577
|
|
21
|
+
yutipy-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
22
|
+
yutipy-2.3.0.dist-info/entry_points.txt,sha256=BrgmanaPjQqKQ3Ip76JLcsPgGANtrBSURf5CNIxl1HA,106
|
|
23
|
+
yutipy-2.3.0.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
|
|
24
|
+
yutipy-2.3.0.dist-info/RECORD,,
|
yutipy-2.2.15.dist-info/RECORD
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
yutipy/__init__.py,sha256=Zrw3cr_6khXp1IgQdZxGcUM9A64GYgPs-6rlqSukW5Q,294
|
|
2
|
-
yutipy/base_clients.py,sha256=FHCyCUQ-qE2Jo5JH-DZCxupoZTlb5ADs8XKDbHDVHwA,21687
|
|
3
|
-
yutipy/deezer.py,sha256=ZI1C5gam8NiNznyyagn5r0Potpg25MXja8UXg-9i9ug,10463
|
|
4
|
-
yutipy/exceptions.py,sha256=zz0XyyZr5xRcmRyw3hdTGaVRcwRn_RSYZdmwmuO0sEM,1379
|
|
5
|
-
yutipy/itunes.py,sha256=dOkz7RqUCIaGggrNWOyxJebrv0f-mF1s9VOG2PVuFbY,7394
|
|
6
|
-
yutipy/kkbox.py,sha256=Pfx-ZgAI9F1cbxjr7MCsMi-QulNt67t60L7y9lNmo5g,11503
|
|
7
|
-
yutipy/lastfm.py,sha256=hOFQOZdf51Gp2m02b4NjKRmQ9yQZ9Yas6MaM78c-oCg,5970
|
|
8
|
-
yutipy/logger.py,sha256=GyLBlfQZ6pLNJ5MbyQSvcD_PkxmFdX41DPq5aeG1z68,1316
|
|
9
|
-
yutipy/models.py,sha256=45M-bNHusaAan_Ta_E9DyvsWujsT-ivbJqIfy2-i3R8,2343
|
|
10
|
-
yutipy/musicyt.py,sha256=n3yaH9qyGlsW2HKPAmqYQNGzhuDH4s5Gh0R5v4JPoeg,9472
|
|
11
|
-
yutipy/spotify.py,sha256=RQvzP62-bIXCLhMocRNFgst0xwDliFLYxU8nzdeWxRo,22616
|
|
12
|
-
yutipy/yutipy_music.py,sha256=MNNh2WT-7GTAykAabLF6p4-0uXiIIbuogswmb-_QqtQ,7272
|
|
13
|
-
yutipy/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
yutipy/cli/config.py,sha256=e5RIq6RxVxxzx30nKVMa06gwyQ258s7U0WA1xvJuR_0,4543
|
|
15
|
-
yutipy/cli/search.py,sha256=8SQw0bjRzRqAg-FuVz9aWjB2KBZqmCf38SyKAQ3rx5E,3025
|
|
16
|
-
yutipy/utils/__init__.py,sha256=AZaqvs6AJwnqwJuodbGnHu702WSUqc8plVC16SppOcU,239
|
|
17
|
-
yutipy/utils/helpers.py,sha256=-iH0bx_sxW3Y3jjl6eTbY6QOBoG5t4obRcp7GGyw3ro,7476
|
|
18
|
-
yutipy-2.2.15.dist-info/licenses/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
|
|
19
|
-
yutipy-2.2.15.dist-info/METADATA,sha256=jUEpFLxQnWbxKn4FVOFNF4hs2avjkoVRQhemNHNYl_4,6369
|
|
20
|
-
yutipy-2.2.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
-
yutipy-2.2.15.dist-info/entry_points.txt,sha256=BrgmanaPjQqKQ3Ip76JLcsPgGANtrBSURf5CNIxl1HA,106
|
|
22
|
-
yutipy-2.2.15.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
|
|
23
|
-
yutipy-2.2.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|