yutipy 0.1.1__py3-none-any.whl → 1.1.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/exceptions.py +4 -0
- yutipy/kkbox.py +320 -0
- yutipy/models.py +55 -55
- yutipy/spotify.py +52 -34
- {yutipy-0.1.1.dist-info → yutipy-1.1.0.dist-info}/LICENSE +21 -21
- {yutipy-0.1.1.dist-info → yutipy-1.1.0.dist-info}/METADATA +152 -140
- yutipy-1.1.0.dist-info/RECORD +15 -0
- {yutipy-0.1.1.dist-info → yutipy-1.1.0.dist-info}/WHEEL +1 -1
- yutipy/__pycache__/__init__.cpython-312.pyc +0 -0
- yutipy/__pycache__/deezer.cpython-312.pyc +0 -0
- yutipy/__pycache__/exceptions.cpython-312.pyc +0 -0
- yutipy/__pycache__/itunes.cpython-312.pyc +0 -0
- yutipy/__pycache__/models.cpython-312.pyc +0 -0
- yutipy/__pycache__/musicyt.cpython-312.pyc +0 -0
- yutipy/__pycache__/spotify.cpython-312.pyc +0 -0
- yutipy/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- yutipy/utils/__pycache__/cheap_utils.cpython-312.pyc +0 -0
- yutipy-0.1.1.dist-info/RECORD +0 -23
- {yutipy-0.1.1.dist-info → yutipy-1.1.0.dist-info}/top_level.txt +0 -0
yutipy/exceptions.py
CHANGED
yutipy/kkbox.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
from pprint import pprint
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
|
|
10
|
+
from yutipy.exceptions import (
|
|
11
|
+
AuthenticationException,
|
|
12
|
+
InvalidResponseException,
|
|
13
|
+
InvalidValueException,
|
|
14
|
+
KKBoxException,
|
|
15
|
+
NetworkException,
|
|
16
|
+
)
|
|
17
|
+
from yutipy.models import MusicInfo
|
|
18
|
+
from yutipy.utils.cheap_utils import are_strings_similar, is_valid_string
|
|
19
|
+
|
|
20
|
+
load_dotenv()
|
|
21
|
+
|
|
22
|
+
KKBOX_CLIENT_ID = os.getenv("KKBOX_CLIENT_ID")
|
|
23
|
+
KKBOX_CLIENT_SECRET = os.getenv("KKBOX_CLIENT_SECRET")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class KKBox:
|
|
27
|
+
"""
|
|
28
|
+
A class to interact with KKBOX Open API.
|
|
29
|
+
|
|
30
|
+
This class reads the ``KKBOX_CLIENT_ID`` and ``KKBOX_CLIENT_SECRET`` from environment variables or the ``.env`` file by default.
|
|
31
|
+
Alternatively, you can manually provide these values when creating an object.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self, client_id: str = KKBOX_CLIENT_ID, client_secret: str = KKBOX_CLIENT_SECRET
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Initializes the KKBox class and sets up the session.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
client_id : str, optional
|
|
43
|
+
The Client ID for the KKBOX Open API. Defaults to ``KKBOX_CLIENT_ID`` from .env file.
|
|
44
|
+
client_secret : str, optional
|
|
45
|
+
The Client secret for the KKBOX Open API. Defaults to ``KKBOX_CLIENT_SECRET`` from .env file.
|
|
46
|
+
"""
|
|
47
|
+
if not client_id or not client_secret:
|
|
48
|
+
raise KKBoxException(
|
|
49
|
+
"Failed to read `KKBOX_CLIENT_ID` and/or `KKBOX_CLIENT_SECRET` from environment variables. Client ID and Client Secret must be provided."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.client_id = client_id
|
|
53
|
+
self.client_secret = client_secret
|
|
54
|
+
self._session = requests.Session()
|
|
55
|
+
self.api_url = "https://api.kkbox.com/v1.1"
|
|
56
|
+
self.__header, self.__expires_in = self.__authenticate()
|
|
57
|
+
self.__start_time = time.time()
|
|
58
|
+
self._is_session_closed = False
|
|
59
|
+
|
|
60
|
+
def __enter__(self):
|
|
61
|
+
"""Enters the runtime context related to this object."""
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
65
|
+
"""Exits the runtime context related to this object."""
|
|
66
|
+
self._close_session()
|
|
67
|
+
|
|
68
|
+
def close_session(self) -> None:
|
|
69
|
+
"""Closes the current session."""
|
|
70
|
+
if not self.is_session_closed:
|
|
71
|
+
self._session.close()
|
|
72
|
+
self._is_session_closed = True
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def is_session_closed(self) -> bool:
|
|
76
|
+
"""Checks if the session is closed."""
|
|
77
|
+
return self._is_session_closed
|
|
78
|
+
|
|
79
|
+
def __authenticate(self) -> tuple:
|
|
80
|
+
"""
|
|
81
|
+
Authenticates with the KKBOX Open API and returns the authorization header.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
dict
|
|
86
|
+
The authorization header.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
token, expires_in = self.__get_access_token()
|
|
90
|
+
return {"Authorization": f"Bearer {token}"}, expires_in
|
|
91
|
+
except Exception as e:
|
|
92
|
+
raise AuthenticationException(
|
|
93
|
+
"Failed to authenticate with KKBOX Open API"
|
|
94
|
+
) from e
|
|
95
|
+
|
|
96
|
+
def __get_access_token(self) -> tuple:
|
|
97
|
+
"""
|
|
98
|
+
Gets the KKBOX Open API token.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
str
|
|
103
|
+
The KKBOX Open API token.
|
|
104
|
+
"""
|
|
105
|
+
auth_string = f"{self.client_id}:{self.client_secret}"
|
|
106
|
+
auth_base64 = base64.b64encode(auth_string.encode("utf-8")).decode("utf-8")
|
|
107
|
+
|
|
108
|
+
url = " https://account.kkbox.com/oauth2/token"
|
|
109
|
+
headers = {
|
|
110
|
+
"Authorization": f"Basic {auth_base64}",
|
|
111
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
112
|
+
}
|
|
113
|
+
data = {"grant_type": "client_credentials"}
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
response = self._session.post(
|
|
117
|
+
url=url, headers=headers, data=data, timeout=30
|
|
118
|
+
)
|
|
119
|
+
response.raise_for_status()
|
|
120
|
+
except requests.RequestException as e:
|
|
121
|
+
raise NetworkException(f"Network error occurred: {e}")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
response_json = response.json()
|
|
125
|
+
return response_json.get("access_token"), response_json.get("expires_in")
|
|
126
|
+
except (KeyError, ValueError) as e:
|
|
127
|
+
raise InvalidResponseException(f"Invalid response received: {e}")
|
|
128
|
+
|
|
129
|
+
def __refresh_token_if_expired(self):
|
|
130
|
+
"""Refreshes the token if it has expired."""
|
|
131
|
+
if time.time() - self.__start_time >= self.__expires_in:
|
|
132
|
+
self.__header, self.__expires_in = self.__authenticate()
|
|
133
|
+
self.__start_time = time.time()
|
|
134
|
+
|
|
135
|
+
def search(
|
|
136
|
+
self, artist: str, song: str, territory: str = "TW"
|
|
137
|
+
) -> Optional[MusicInfo]:
|
|
138
|
+
"""
|
|
139
|
+
Searches for a song by artist and title.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
artist : str
|
|
144
|
+
The name of the artist.
|
|
145
|
+
song : str
|
|
146
|
+
The title of the song.
|
|
147
|
+
territory : str
|
|
148
|
+
Two-letter country codes from ISO 3166-1 alpha-2.
|
|
149
|
+
Allowed values: ``HK``, ``JP``, ``MY``, ``SG``, ``TW``.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
Optional[MusicInfo_]
|
|
154
|
+
The music information if found, otherwise None.
|
|
155
|
+
"""
|
|
156
|
+
if not is_valid_string(artist) or not is_valid_string(song):
|
|
157
|
+
raise InvalidValueException(
|
|
158
|
+
"Artist and song names must be valid strings and can't be empty."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
self.__refresh_token_if_expired()
|
|
162
|
+
|
|
163
|
+
query = f"?q={artist} - {song}&type=track,album&territory={territory}&limit=10"
|
|
164
|
+
query_url = f"{self.api_url}/search{query}"
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
response = self._session.get(query_url, headers=self.__header, timeout=30)
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
except requests.RequestException as e:
|
|
170
|
+
raise NetworkException(f"Network error occurred: {e}")
|
|
171
|
+
|
|
172
|
+
if response.status_code != 200:
|
|
173
|
+
raise KKBoxException(f"Failed to search for music: {response.json()}")
|
|
174
|
+
|
|
175
|
+
return self._find_music_info(artist, song, response.json())
|
|
176
|
+
|
|
177
|
+
def _find_music_info(
|
|
178
|
+
self, artist: str, song: str, response_json: dict
|
|
179
|
+
) -> Optional[MusicInfo]:
|
|
180
|
+
"""
|
|
181
|
+
Finds the music information from the search results.
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
artist : str
|
|
186
|
+
The name of the artist.
|
|
187
|
+
song : str
|
|
188
|
+
The title of the song.
|
|
189
|
+
response_json : dict
|
|
190
|
+
The JSON response from the API.
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
Optional[MusicInfo]
|
|
195
|
+
The music information if found, otherwise None.
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
for track in response_json["tracks"]["data"]:
|
|
199
|
+
music_info = self._find_track(song, artist, track)
|
|
200
|
+
if music_info:
|
|
201
|
+
return music_info
|
|
202
|
+
except KeyError:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
for album in response_json["albums"]["data"]:
|
|
207
|
+
music_info = self._find_album(song, artist, album)
|
|
208
|
+
if music_info:
|
|
209
|
+
return music_info
|
|
210
|
+
except KeyError:
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def _find_track(self, song: str, artist: str, track: dict) -> Optional[MusicInfo]:
|
|
216
|
+
"""
|
|
217
|
+
Finds the track information from the search results.
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
song : str
|
|
222
|
+
The title of the song.
|
|
223
|
+
artist : str
|
|
224
|
+
The name of the artist.
|
|
225
|
+
track : dict
|
|
226
|
+
A single track from the search results.
|
|
227
|
+
artist_ids : list
|
|
228
|
+
A list of artist IDs.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
Optional[MusicInfo]
|
|
233
|
+
The music information if found, otherwise None.
|
|
234
|
+
"""
|
|
235
|
+
if not are_strings_similar(track["name"], song):
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
artists_name = track["album"]["artist"]["name"]
|
|
239
|
+
matching_artists = (
|
|
240
|
+
artists_name if are_strings_similar(artists_name, artist) else None
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
if matching_artists:
|
|
244
|
+
return MusicInfo(
|
|
245
|
+
album_art=track["album"]["images"][2]["url"],
|
|
246
|
+
album_title=track["album"]["name"],
|
|
247
|
+
album_type=None,
|
|
248
|
+
artists=artists_name,
|
|
249
|
+
genre=None,
|
|
250
|
+
id=track["id"],
|
|
251
|
+
isrc=track["isrc"],
|
|
252
|
+
lyrics=None,
|
|
253
|
+
release_date=track["album"]["release_date"],
|
|
254
|
+
tempo=None,
|
|
255
|
+
title=track["name"],
|
|
256
|
+
type="track",
|
|
257
|
+
upc=None,
|
|
258
|
+
url=track["url"],
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
def _find_album(self, song: str, artist: str, album: dict) -> Optional[MusicInfo]:
|
|
264
|
+
"""
|
|
265
|
+
Finds the album information from the search results.
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
----------
|
|
269
|
+
song : str
|
|
270
|
+
The title of the song.
|
|
271
|
+
artist : str
|
|
272
|
+
The name of the artist.
|
|
273
|
+
album : dict
|
|
274
|
+
A single album from the search results.
|
|
275
|
+
artist_ids : list
|
|
276
|
+
A list of artist IDs.
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
Optional[MusicInfo]
|
|
281
|
+
The music information if found, otherwise None.
|
|
282
|
+
"""
|
|
283
|
+
if not are_strings_similar(album["name"], song):
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
artists_name = album["artist"]["name"]
|
|
287
|
+
matching_artists = (
|
|
288
|
+
artists_name if are_strings_similar(artists_name, artist) else None
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if matching_artists:
|
|
292
|
+
return MusicInfo(
|
|
293
|
+
album_art=album["images"][2]["url"],
|
|
294
|
+
album_title=album["name"],
|
|
295
|
+
album_type=None,
|
|
296
|
+
artists=artists_name,
|
|
297
|
+
genre=None,
|
|
298
|
+
id=album["id"],
|
|
299
|
+
isrc=None,
|
|
300
|
+
lyrics=None,
|
|
301
|
+
release_date=album["release_date"],
|
|
302
|
+
tempo=None,
|
|
303
|
+
title=album["name"],
|
|
304
|
+
type="album",
|
|
305
|
+
upc=None,
|
|
306
|
+
url=album["url"],
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
kkbox = KKBox(KKBOX_CLIENT_ID, KKBOX_CLIENT_SECRET)
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
artist_name = input("Artist Name: ")
|
|
317
|
+
song_name = input("Song Name: ")
|
|
318
|
+
pprint(kkbox.search(artist_name, song_name))
|
|
319
|
+
finally:
|
|
320
|
+
kkbox.close_session()
|
yutipy/models.py
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@dataclass
|
|
6
|
-
class MusicInfo:
|
|
7
|
-
"""
|
|
8
|
-
A data class to store music information.
|
|
9
|
-
|
|
10
|
-
Attributes
|
|
11
|
-
----------
|
|
12
|
-
album_art : Optional[str]
|
|
13
|
-
URL to the album art.
|
|
14
|
-
album_title : Optional[str]
|
|
15
|
-
Title of the album.
|
|
16
|
-
album_type : Optional[str]
|
|
17
|
-
Type of the album (e.g., album, single).
|
|
18
|
-
artists : str
|
|
19
|
-
Name(s) of the artist(s).
|
|
20
|
-
genre : Optional[str]
|
|
21
|
-
Genre of the music.
|
|
22
|
-
id : str
|
|
23
|
-
Unique identifier for the music.
|
|
24
|
-
isrc : Optional[str]
|
|
25
|
-
International Standard Recording Code.
|
|
26
|
-
lyrics : Optional[str]
|
|
27
|
-
Lyrics of the song.
|
|
28
|
-
release_date : Optional[str]
|
|
29
|
-
Release date of the music.
|
|
30
|
-
tempo : Optional[float]
|
|
31
|
-
Tempo of the music in BPM.
|
|
32
|
-
title : str
|
|
33
|
-
Title of the music.
|
|
34
|
-
type : Optional[str]
|
|
35
|
-
Type of the music (e.g., track, album).
|
|
36
|
-
upc : Optional[str]
|
|
37
|
-
Universal Product Code.
|
|
38
|
-
url : str
|
|
39
|
-
URL to the music on the platform.
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
album_art: Optional[str]
|
|
43
|
-
album_title: Optional[str]
|
|
44
|
-
album_type: Optional[str]
|
|
45
|
-
artists: str
|
|
46
|
-
genre: Optional[str]
|
|
47
|
-
id: str
|
|
48
|
-
isrc: Optional[str]
|
|
49
|
-
lyrics: Optional[str]
|
|
50
|
-
release_date: Optional[str]
|
|
51
|
-
tempo: Optional[float]
|
|
52
|
-
title: str
|
|
53
|
-
type: Optional[str]
|
|
54
|
-
upc: Optional[str]
|
|
55
|
-
url: str
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class MusicInfo:
|
|
7
|
+
"""
|
|
8
|
+
A data class to store music information.
|
|
9
|
+
|
|
10
|
+
Attributes
|
|
11
|
+
----------
|
|
12
|
+
album_art : Optional[str]
|
|
13
|
+
URL to the album art.
|
|
14
|
+
album_title : Optional[str]
|
|
15
|
+
Title of the album.
|
|
16
|
+
album_type : Optional[str]
|
|
17
|
+
Type of the album (e.g., album, single).
|
|
18
|
+
artists : str
|
|
19
|
+
Name(s) of the artist(s).
|
|
20
|
+
genre : Optional[str]
|
|
21
|
+
Genre of the music.
|
|
22
|
+
id : str
|
|
23
|
+
Unique identifier for the music.
|
|
24
|
+
isrc : Optional[str]
|
|
25
|
+
International Standard Recording Code.
|
|
26
|
+
lyrics : Optional[str]
|
|
27
|
+
Lyrics of the song.
|
|
28
|
+
release_date : Optional[str]
|
|
29
|
+
Release date of the music.
|
|
30
|
+
tempo : Optional[float]
|
|
31
|
+
Tempo of the music in BPM.
|
|
32
|
+
title : str
|
|
33
|
+
Title of the music.
|
|
34
|
+
type : Optional[str]
|
|
35
|
+
Type of the music (e.g., track, album).
|
|
36
|
+
upc : Optional[str]
|
|
37
|
+
Universal Product Code.
|
|
38
|
+
url : str
|
|
39
|
+
URL to the music on the platform.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
album_art: Optional[str]
|
|
43
|
+
album_title: Optional[str]
|
|
44
|
+
album_type: Optional[str]
|
|
45
|
+
artists: str
|
|
46
|
+
genre: Optional[str]
|
|
47
|
+
id: str
|
|
48
|
+
isrc: Optional[str]
|
|
49
|
+
lyrics: Optional[str]
|
|
50
|
+
release_date: Optional[str]
|
|
51
|
+
tempo: Optional[float]
|
|
52
|
+
title: str
|
|
53
|
+
type: Optional[str]
|
|
54
|
+
upc: Optional[str]
|
|
55
|
+
url: str
|
yutipy/spotify.py
CHANGED
|
@@ -23,20 +23,22 @@ from yutipy.utils.cheap_utils import (
|
|
|
23
23
|
|
|
24
24
|
load_dotenv()
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
|
27
|
+
SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class Spotify:
|
|
31
31
|
"""
|
|
32
32
|
A class to interact with the Spotify API.
|
|
33
33
|
|
|
34
|
-
This class reads the ``
|
|
35
|
-
Alternatively,
|
|
34
|
+
This class reads the ``SPOTIFY_CLIENT_ID`` and ``SPOTIFY_CLIENT_SECRET`` from environment variables or the ``.env`` file by default.
|
|
35
|
+
Alternatively, you can manually provide these values when creating an object.
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
def __init__(
|
|
39
|
-
self,
|
|
39
|
+
self,
|
|
40
|
+
client_id: str = SPOTIFY_CLIENT_ID,
|
|
41
|
+
client_secret: str = SPOTIFY_CLIENT_SECRET,
|
|
40
42
|
) -> None:
|
|
41
43
|
"""
|
|
42
44
|
Initializes the Spotify class and sets up the session.
|
|
@@ -44,20 +46,20 @@ class Spotify:
|
|
|
44
46
|
Parameters
|
|
45
47
|
----------
|
|
46
48
|
client_id : str, optional
|
|
47
|
-
The
|
|
49
|
+
The Client ID for the Spotify API. Defaults to ``SPOTIFY_CLIENT_ID`` from .env file.
|
|
48
50
|
client_secret : str, optional
|
|
49
|
-
The
|
|
51
|
+
The Client secret for the Spotify API. Defaults to ``SPOTIFY_CLIENT_SECRET`` from .env file.
|
|
50
52
|
"""
|
|
51
53
|
if not client_id or not client_secret:
|
|
52
54
|
raise SpotifyException(
|
|
53
|
-
"Failed to read
|
|
55
|
+
"Failed to read `SPOTIFY_CLIENT_ID` and/or `SPOTIFY_CLIENT_SECRET` from environment variables. Client ID and Client Secret must be provided."
|
|
54
56
|
)
|
|
55
57
|
|
|
56
58
|
self.client_id = client_id
|
|
57
59
|
self.client_secret = client_secret
|
|
58
60
|
self._session = requests.Session()
|
|
59
61
|
self.api_url = "https://api.spotify.com/v1"
|
|
60
|
-
self.__header = self.__authenticate()
|
|
62
|
+
self.__header, self.__expires_in = self.__authenticate()
|
|
61
63
|
self.__start_time = time.time()
|
|
62
64
|
self._is_session_closed = False
|
|
63
65
|
|
|
@@ -80,7 +82,7 @@ class Spotify:
|
|
|
80
82
|
"""Checks if the session is closed."""
|
|
81
83
|
return self._is_session_closed
|
|
82
84
|
|
|
83
|
-
def __authenticate(self) ->
|
|
85
|
+
def __authenticate(self) -> tuple:
|
|
84
86
|
"""
|
|
85
87
|
Authenticates with the Spotify API and returns the authorization header.
|
|
86
88
|
|
|
@@ -90,14 +92,14 @@ class Spotify:
|
|
|
90
92
|
The authorization header.
|
|
91
93
|
"""
|
|
92
94
|
try:
|
|
93
|
-
token = self.__get_spotify_token()
|
|
94
|
-
return {"Authorization": f"Bearer {token}"}
|
|
95
|
+
token, expires_in = self.__get_spotify_token()
|
|
96
|
+
return {"Authorization": f"Bearer {token}"}, expires_in
|
|
95
97
|
except Exception as e:
|
|
96
98
|
raise AuthenticationException(
|
|
97
99
|
"Failed to authenticate with Spotify API"
|
|
98
100
|
) from e
|
|
99
101
|
|
|
100
|
-
def __get_spotify_token(self) ->
|
|
102
|
+
def __get_spotify_token(self) -> tuple:
|
|
101
103
|
"""
|
|
102
104
|
Gets the Spotify API token.
|
|
103
105
|
|
|
@@ -125,14 +127,16 @@ class Spotify:
|
|
|
125
127
|
raise NetworkException(f"Network error occurred: {e}")
|
|
126
128
|
|
|
127
129
|
try:
|
|
128
|
-
|
|
130
|
+
response_json = response.json()
|
|
131
|
+
return response_json.get("access_token"), response_json.get("expires_in")
|
|
129
132
|
except (KeyError, ValueError) as e:
|
|
130
133
|
raise InvalidResponseException(f"Invalid response received: {e}")
|
|
131
134
|
|
|
132
135
|
def __refresh_token_if_expired(self):
|
|
133
136
|
"""Refreshes the token if it has expired."""
|
|
134
|
-
if time.time() - self.__start_time >=
|
|
135
|
-
self.__header = self.__authenticate()
|
|
137
|
+
if time.time() - self.__start_time >= self.__expires_in:
|
|
138
|
+
self.__header, self.__expires_in = self.__authenticate()
|
|
139
|
+
self.__start_time = time.time()
|
|
136
140
|
|
|
137
141
|
def search(self, artist: str, song: str) -> Optional[MusicInfo]:
|
|
138
142
|
"""
|
|
@@ -155,23 +159,37 @@ class Spotify:
|
|
|
155
159
|
"Artist and song names must be valid strings and can't be empty."
|
|
156
160
|
)
|
|
157
161
|
|
|
158
|
-
|
|
162
|
+
music_info = None
|
|
163
|
+
queries = [
|
|
164
|
+
f"?q=artist:{artist} track:{song}&type=track&limit=10",
|
|
165
|
+
f"?q=artist:{artist} album:{song}&type=album&limit=10",
|
|
166
|
+
]
|
|
159
167
|
|
|
160
|
-
query
|
|
161
|
-
|
|
168
|
+
for query in queries:
|
|
169
|
+
if music_info:
|
|
170
|
+
return music_info
|
|
162
171
|
|
|
163
|
-
|
|
164
|
-
response = self._session.get(query_url, headers=self.__header, timeout=30)
|
|
165
|
-
response.raise_for_status()
|
|
166
|
-
except requests.RequestException as e:
|
|
167
|
-
raise NetworkException(f"Network error occurred: {e}")
|
|
172
|
+
self.__refresh_token_if_expired()
|
|
168
173
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
query_url = f"{self.api_url}/search{query}"
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
response = self._session.get(
|
|
178
|
+
query_url, headers=self.__header, timeout=30
|
|
179
|
+
)
|
|
180
|
+
response.raise_for_status()
|
|
181
|
+
except requests.RequestException as e:
|
|
182
|
+
raise NetworkException(f"Network error occurred: {e}")
|
|
183
|
+
|
|
184
|
+
if response.status_code != 200:
|
|
185
|
+
raise SpotifyException(f"Failed to search for music: {response.json()}")
|
|
186
|
+
|
|
187
|
+
artist_ids = self._get_artists_ids(artist)
|
|
188
|
+
music_info = self._find_music_info(
|
|
189
|
+
artist, song, response.json(), artist_ids
|
|
190
|
+
)
|
|
173
191
|
|
|
174
|
-
return
|
|
192
|
+
return music_info
|
|
175
193
|
|
|
176
194
|
def search_advanced(
|
|
177
195
|
self, artist: str, song: str, isrc: str = None, upc: str = None
|
|
@@ -281,7 +299,7 @@ class Spotify:
|
|
|
281
299
|
"""
|
|
282
300
|
try:
|
|
283
301
|
for track in response_json["tracks"]["items"]:
|
|
284
|
-
music_info = self.
|
|
302
|
+
music_info = self._find_track(song, artist, track, artist_ids)
|
|
285
303
|
if music_info:
|
|
286
304
|
return music_info
|
|
287
305
|
except KeyError:
|
|
@@ -297,7 +315,7 @@ class Spotify:
|
|
|
297
315
|
|
|
298
316
|
return None
|
|
299
317
|
|
|
300
|
-
def
|
|
318
|
+
def _find_track(
|
|
301
319
|
self, song: str, artist: str, track: dict, artist_ids: list
|
|
302
320
|
) -> Optional[MusicInfo]:
|
|
303
321
|
"""
|
|
@@ -403,11 +421,11 @@ class Spotify:
|
|
|
403
421
|
|
|
404
422
|
|
|
405
423
|
if __name__ == "__main__":
|
|
406
|
-
|
|
424
|
+
spotify = Spotify(SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET)
|
|
407
425
|
|
|
408
426
|
try:
|
|
409
427
|
artist_name = input("Artist Name: ")
|
|
410
428
|
song_name = input("Song Name: ")
|
|
411
|
-
pprint(
|
|
429
|
+
pprint(spotify.search(artist_name, song_name))
|
|
412
430
|
finally:
|
|
413
|
-
|
|
431
|
+
spotify.close_session()
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Cheap Nightbot
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cheap Nightbot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,140 +1,152 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: yutipy
|
|
3
|
-
Version:
|
|
4
|
-
Summary: A simple package for retrieving music information from various music platforms APIs.
|
|
5
|
-
Author: Cheap Nightbot
|
|
6
|
-
Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
7
|
-
Maintainer-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
8
|
-
License: MIT License
|
|
9
|
-
|
|
10
|
-
Copyright (c) 2025 Cheap Nightbot
|
|
11
|
-
|
|
12
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
-
in the Software without restriction, including without limitation the rights
|
|
15
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
-
furnished to do so, subject to the following conditions:
|
|
18
|
-
|
|
19
|
-
The above copyright notice and this permission notice shall be included in all
|
|
20
|
-
copies or substantial portions of the Software.
|
|
21
|
-
|
|
22
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
-
SOFTWARE.
|
|
29
|
-
|
|
30
|
-
Project-URL: Homepage, https://github.com/CheapNightbot/yutipy
|
|
31
|
-
Project-URL: Documentation, https://yutipy.readthedocs.io/
|
|
32
|
-
Project-URL: Repository, https://github.com/CheapNightbot/yutipy.git
|
|
33
|
-
Project-URL: Issues, https://github.com/CheapNightbot/yutipy/issues
|
|
34
|
-
Project-URL: Changelog, https://github.com/CheapNightbot/yutipy/blob/master/CHANGELOG.md
|
|
35
|
-
Project-URL: funding, https://ko-fi.com/cheapnightbot
|
|
36
|
-
Keywords: music,API,Deezer,iTunes,Spotify,YouTube Music,search,retrieve,information,yutify
|
|
37
|
-
Classifier: Development Status :: 4 - Beta
|
|
38
|
-
Classifier: Intended Audience :: Developers
|
|
39
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
40
|
-
Classifier: Programming Language :: Python :: 3
|
|
41
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
42
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
43
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
44
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
45
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
47
|
-
Classifier: Operating System :: OS Independent
|
|
48
|
-
Requires-Python: >=3.8
|
|
49
|
-
Description-Content-Type: text/markdown
|
|
50
|
-
License-File: LICENSE
|
|
51
|
-
Requires-Dist: python-dotenv==1.0.1
|
|
52
|
-
Requires-Dist: rapidfuzz==3.12.1
|
|
53
|
-
Requires-Dist: requests==2.32.3
|
|
54
|
-
Requires-Dist: ytmusicapi==1.10.1
|
|
55
|
-
Provides-Extra: dev
|
|
56
|
-
Requires-Dist: pytest; extra == "dev"
|
|
57
|
-
|
|
58
|
-
<p align="center">
|
|
59
|
-
<img src="https://raw.githubusercontent.com/CheapNightbot/yutipy/main/docs/_static/yutipy_header.png" alt="yutipy" />
|
|
60
|
-
</p>
|
|
61
|
-
|
|
62
|
-
<p align="center">
|
|
63
|
-
<a href="https://github.com/CheapNightbot/yutipy/actions/workflows/tests.yml">
|
|
64
|
-
<img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/cheapnightbot/yutipy/pytest-unit-testing.yml?style=for-the-badge&label=Pytest">
|
|
65
|
-
</a>
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
</
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- [
|
|
89
|
-
- [Installation](#installation)
|
|
90
|
-
- [Usage Example](#usage-example)
|
|
91
|
-
- [Contributing](#contributing)
|
|
92
|
-
- [License](#license)
|
|
93
|
-
|
|
94
|
-
## Features
|
|
95
|
-
|
|
96
|
-
- Simple & Easy integration with popular music APIs.
|
|
97
|
-
- Search for music by artist and song title across multiple platforms.
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: yutipy
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: A simple package for retrieving music information from various music platforms APIs.
|
|
5
|
+
Author: Cheap Nightbot
|
|
6
|
+
Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
7
|
+
Maintainer-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Cheap Nightbot
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Project-URL: Homepage, https://github.com/CheapNightbot/yutipy
|
|
31
|
+
Project-URL: Documentation, https://yutipy.readthedocs.io/
|
|
32
|
+
Project-URL: Repository, https://github.com/CheapNightbot/yutipy.git
|
|
33
|
+
Project-URL: Issues, https://github.com/CheapNightbot/yutipy/issues
|
|
34
|
+
Project-URL: Changelog, https://github.com/CheapNightbot/yutipy/blob/master/CHANGELOG.md
|
|
35
|
+
Project-URL: funding, https://ko-fi.com/cheapnightbot
|
|
36
|
+
Keywords: music,API,Deezer,iTunes,Spotify,YouTube Music,search,retrieve,information,yutify
|
|
37
|
+
Classifier: Development Status :: 4 - Beta
|
|
38
|
+
Classifier: Intended Audience :: Developers
|
|
39
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
40
|
+
Classifier: Programming Language :: Python :: 3
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
47
|
+
Classifier: Operating System :: OS Independent
|
|
48
|
+
Requires-Python: >=3.8
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
License-File: LICENSE
|
|
51
|
+
Requires-Dist: python-dotenv==1.0.1
|
|
52
|
+
Requires-Dist: rapidfuzz==3.12.1
|
|
53
|
+
Requires-Dist: requests==2.32.3
|
|
54
|
+
Requires-Dist: ytmusicapi==1.10.1
|
|
55
|
+
Provides-Extra: dev
|
|
56
|
+
Requires-Dist: pytest; extra == "dev"
|
|
57
|
+
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="https://raw.githubusercontent.com/CheapNightbot/yutipy/main/docs/_static/yutipy_header.png" alt="yutipy" />
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
<p align="center">
|
|
63
|
+
<a href="https://github.com/CheapNightbot/yutipy/actions/workflows/tests.yml">
|
|
64
|
+
<img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/cheapnightbot/yutipy/pytest-unit-testing.yml?style=for-the-badge&label=Pytest">
|
|
65
|
+
</a>
|
|
66
|
+
<a href="https://pypi.org/project/yutipy/">
|
|
67
|
+
<img src="https://img.shields.io/pypi/v/yutipy?style=for-the-badge" alt="PyPI" />
|
|
68
|
+
</a>
|
|
69
|
+
<a href="https://yutipy.readthedocs.io/en/latest/">
|
|
70
|
+
<img src="https://img.shields.io/readthedocs/yutipy?style=for-the-badge" alt="Documentation Status" />
|
|
71
|
+
</a>
|
|
72
|
+
<a href="https://github.com/CheapNightbot/yutipy/blob/master/LICENSE">
|
|
73
|
+
<img src="https://img.shields.io/github/license/CheapNightbot/yutipy?style=for-the-badge" alt="License" />
|
|
74
|
+
</a>
|
|
75
|
+
<a href="https://github.com/CheapNightbot/yutipy/stargazers">
|
|
76
|
+
<img src="https://img.shields.io/github/stars/CheapNightbot/yutipy?style=for-the-badge" alt="Stars" />
|
|
77
|
+
</a>
|
|
78
|
+
<a href="https://github.com/CheapNightbot/yutipy/issues">
|
|
79
|
+
<img src="https://img.shields.io/github/issues/CheapNightbot/yutipy?style=for-the-badge" alt="Issues" />
|
|
80
|
+
</a>
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
A _**simple**_ Python package for searching and retrieving music information from various music platforms APIs, including Deezer, iTunes, Spotify, and YouTube Music.
|
|
84
|
+
|
|
85
|
+
## Table of Contents
|
|
86
|
+
|
|
87
|
+
- [Features](#features)
|
|
88
|
+
- [Available Music Platforms](#available-music-platforms)
|
|
89
|
+
- [Installation](#installation)
|
|
90
|
+
- [Usage Example](#usage-example)
|
|
91
|
+
- [Contributing](#contributing)
|
|
92
|
+
- [License](#license)
|
|
93
|
+
|
|
94
|
+
## Features
|
|
95
|
+
|
|
96
|
+
- Simple & Easy integration with popular music APIs.
|
|
97
|
+
- Search for music by artist and song title across multiple platforms.
|
|
98
|
+
- It uses `RapidFuzz` to compare & return the best match so that you can be sure you got what you asked for without having to worry and doing all that work by yourself.
|
|
99
|
+
- Retrieve detailed music information, including album art, release dates, lyrics, ISRC, and UPC codes.
|
|
100
|
+
|
|
101
|
+
### Available Music Platforms
|
|
102
|
+
|
|
103
|
+
Right now, the following music platforms are available in yutipy for searching music. New platforms will be added in the future.
|
|
104
|
+
Feel free to request any music platform you would like me to add by opening an issue on [GitHub](https://github.com/CheapNightbot/yutipy/issues) or by emailing me.
|
|
105
|
+
|
|
106
|
+
- `Deezer`: https://www.deezer.com
|
|
107
|
+
- `iTunes`: https://music.apple.com
|
|
108
|
+
- `KKBOX`: https://www.kkbox.com
|
|
109
|
+
- `Spotify`: https://spotify.com
|
|
110
|
+
- `YouTube Music`: https://music.youtube.com
|
|
111
|
+
|
|
112
|
+
## Installation
|
|
113
|
+
|
|
114
|
+
You can install the package using pip. Make sure you have Python 3.8 or higher installed.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install -U yutipy
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Usage Example
|
|
121
|
+
|
|
122
|
+
Here's a quick example of how to use the `yutipy` package to search for a song:
|
|
123
|
+
|
|
124
|
+
### Deezer
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from yutipy.deezer import Deezer
|
|
128
|
+
|
|
129
|
+
with Deezer() as deezer:
|
|
130
|
+
result = deezer.search("Artist Name", "Song Title")
|
|
131
|
+
print(result)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For more usage examples, see the [Usage Examples](https://yutipy.readthedocs.io/en/latest/usage_examples.html) page in docs.
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
Contributions are welcome! Please follow these steps:
|
|
139
|
+
|
|
140
|
+
1. Fork the repository.
|
|
141
|
+
2. Optionally, create an issue to discuss the changes you plan to make.
|
|
142
|
+
3. Create a new branch linked to that issue.
|
|
143
|
+
4. Make your changes in the new branch.
|
|
144
|
+
5. Write tests if you add new functionality.
|
|
145
|
+
6. Ensure all tests pass before opening a pull request.
|
|
146
|
+
7. Open a pull request for review.
|
|
147
|
+
|
|
148
|
+
Thank you for your contributions!
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
yutipy/__init__.py,sha256=t0w-pRLjIGD3UtDyd-JpeL-RrMona7YTV_0lZCpGLEc,232
|
|
2
|
+
yutipy/deezer.py,sha256=TbPk6f1ytrtk9GHXOoRZ948fcgOi0619-rQC9xfTYu0,8641
|
|
3
|
+
yutipy/exceptions.py,sha256=4L0Oe1PwFP34LoFTy-Fruipk7uB-JkaackRmkjlaZJU,1138
|
|
4
|
+
yutipy/itunes.py,sha256=NhrllA3U0PUY3dNoMyiG1oZuyvdMQKYRBHzYSkt8ikM,5843
|
|
5
|
+
yutipy/kkbox.py,sha256=l-I1FZjKzLLqjN0hO2hvz6AdNuYvD1B4XI36m3sWmfM,10123
|
|
6
|
+
yutipy/models.py,sha256=RnfWjoNO1UoNsRvNRYd-4TpkU-4jHKu5t394vIqOOgw,1360
|
|
7
|
+
yutipy/musicyt.py,sha256=kvRYuhlNVuvy-WahJq895T3JAzmGTTiKB89r_uTMTAI,7155
|
|
8
|
+
yutipy/spotify.py,sha256=CZA9XF0EV5wSrLrN9cURtiFKSEHsIvqVlXLKkvb4JEM,13609
|
|
9
|
+
yutipy/utils/__init__.py,sha256=7UFcFZ7fBtNXOTngjnRD3MeobT3x5UT2Gag94TXVgLk,169
|
|
10
|
+
yutipy/utils/cheap_utils.py,sha256=LIuEHib_97NuLahXxdHJUD9v-ccXNUc3NrLYk8EQ52A,1652
|
|
11
|
+
yutipy-1.1.0.dist-info/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
|
|
12
|
+
yutipy-1.1.0.dist-info/METADATA,sha256=2EY4cfuWnLA1fH4E1G1qp8x3o53M8bzAYSX6QPdxAms,6467
|
|
13
|
+
yutipy-1.1.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
|
14
|
+
yutipy-1.1.0.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
|
|
15
|
+
yutipy-1.1.0.dist-info/RECORD,,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
yutipy-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
yutipy/__init__.py,sha256=t0w-pRLjIGD3UtDyd-JpeL-RrMona7YTV_0lZCpGLEc,232
|
|
2
|
-
yutipy/deezer.py,sha256=TbPk6f1ytrtk9GHXOoRZ948fcgOi0619-rQC9xfTYu0,8641
|
|
3
|
-
yutipy/exceptions.py,sha256=QDfOkaeZtSNg1lhXZ872yNJwbof68eVHDezZeZh8DiU,1029
|
|
4
|
-
yutipy/itunes.py,sha256=NhrllA3U0PUY3dNoMyiG1oZuyvdMQKYRBHzYSkt8ikM,5843
|
|
5
|
-
yutipy/models.py,sha256=-UEi05on5XsKjFSqUNt1yZnLod_A9upqh2nIPWTtEEI,1415
|
|
6
|
-
yutipy/musicyt.py,sha256=kvRYuhlNVuvy-WahJq895T3JAzmGTTiKB89r_uTMTAI,7155
|
|
7
|
-
yutipy/spotify.py,sha256=YYTF1git7mRVFWWGM1TILIIyZMPPDbD_F_BLjfqU6cI,12906
|
|
8
|
-
yutipy/__pycache__/__init__.cpython-312.pyc,sha256=q-CaghM107NTOH0ZfH9LKDDUZJuGA_teF1QyoO1wNb8,392
|
|
9
|
-
yutipy/__pycache__/deezer.cpython-312.pyc,sha256=UfRxyBobsocMxkpF4HP06EpFsAz96ZHU4QlYn8EzQcY,10588
|
|
10
|
-
yutipy/__pycache__/exceptions.cpython-312.pyc,sha256=Zc73Tr79emWJ9pmpAVoL12YnBBOWJulV1hxtSso4hy8,2096
|
|
11
|
-
yutipy/__pycache__/itunes.cpython-312.pyc,sha256=59fJpxL2ZjQoyzq_1U6sesJEuNrfa62kvtPYeSwzZNg,7521
|
|
12
|
-
yutipy/__pycache__/models.cpython-312.pyc,sha256=KJUa3-C67TMq1EAGuQlZYT99JS1AEDUi74KY8bEHkkg,1848
|
|
13
|
-
yutipy/__pycache__/musicyt.cpython-312.pyc,sha256=y508WR8Cl8DtisJli3FwNDEC6CJZajiZdcWJLN7xXyY,9176
|
|
14
|
-
yutipy/__pycache__/spotify.cpython-312.pyc,sha256=0Knor0AN6zG6ATYSGL0YPb6b7rrT2krnpBv-e2ynam8,15870
|
|
15
|
-
yutipy/utils/__init__.py,sha256=7UFcFZ7fBtNXOTngjnRD3MeobT3x5UT2Gag94TXVgLk,169
|
|
16
|
-
yutipy/utils/cheap_utils.py,sha256=LIuEHib_97NuLahXxdHJUD9v-ccXNUc3NrLYk8EQ52A,1652
|
|
17
|
-
yutipy/utils/__pycache__/__init__.cpython-312.pyc,sha256=_ldEMv66ELlrY1M7fN_2r5XySvgAsqTE0U8ASGXx8bs,286
|
|
18
|
-
yutipy/utils/__pycache__/cheap_utils.cpython-312.pyc,sha256=i6nRmXcRtCkTzKQ1mWyjN7Xz4NapqgBdXA33so8xgLU,2376
|
|
19
|
-
yutipy-0.1.1.dist-info/LICENSE,sha256=Tymbhvk1F_6BDFE0X8BBiQp7cBCjbzKfRAITmvxJIyA,1092
|
|
20
|
-
yutipy-0.1.1.dist-info/METADATA,sha256=nWGLHD2t-1GvvU9vUXcU5TtvLe05_w2G0U8pKhWw_Uc,5865
|
|
21
|
-
yutipy-0.1.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
22
|
-
yutipy-0.1.1.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
|
|
23
|
-
yutipy-0.1.1.dist-info/RECORD,,
|
|
File without changes
|