yutipy 0.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/__init__.py +13 -0
- 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/deezer.py +277 -0
- yutipy/exceptions.py +52 -0
- yutipy/itunes.py +189 -0
- yutipy/models.py +55 -0
- yutipy/musicyt.py +247 -0
- yutipy/spotify.py +413 -0
- yutipy/utils/__init__.py +7 -0
- yutipy/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- yutipy/utils/__pycache__/cheap_utils.cpython-312.pyc +0 -0
- yutipy/utils/cheap_utils.py +50 -0
- yutipy-0.1.0.dist-info/LICENSE +21 -0
- yutipy-0.1.0.dist-info/METADATA +119 -0
- yutipy-0.1.0.dist-info/RECORD +23 -0
- yutipy-0.1.0.dist-info/WHEEL +5 -0
- yutipy-0.1.0.dist-info/top_level.txt +1 -0
yutipy/__init__.py
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
yutipy/deezer.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from pprint import pprint
|
|
2
|
+
from typing import Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from yutipy.exceptions import (
|
|
7
|
+
DeezerException,
|
|
8
|
+
InvalidResponseException,
|
|
9
|
+
InvalidValueException,
|
|
10
|
+
NetworkException,
|
|
11
|
+
)
|
|
12
|
+
from yutipy.models import MusicInfo
|
|
13
|
+
from yutipy.utils.cheap_utils import are_strings_similar, is_valid_string
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Deezer:
|
|
17
|
+
"""A class to interact with the Deezer API."""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
"""Initializes the Deezer class and sets up the session."""
|
|
21
|
+
self._session = requests.Session()
|
|
22
|
+
self.api_url = "https://api.deezer.com"
|
|
23
|
+
self._is_session_closed = False
|
|
24
|
+
|
|
25
|
+
def __enter__(self) -> "Deezer":
|
|
26
|
+
"""Enters the runtime context related to this object."""
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
|
30
|
+
"""Exits the runtime context related to this object."""
|
|
31
|
+
self._close_session()
|
|
32
|
+
|
|
33
|
+
def _close_session(self) -> None:
|
|
34
|
+
"""Closes the current session."""
|
|
35
|
+
if not self.is_session_closed:
|
|
36
|
+
self._session.close()
|
|
37
|
+
self._is_session_closed = True
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def is_session_closed(self) -> bool:
|
|
41
|
+
"""Checks if the session is closed."""
|
|
42
|
+
return self._is_session_closed
|
|
43
|
+
|
|
44
|
+
def search(self, artist: str, song: str) -> Optional[MusicInfo]:
|
|
45
|
+
"""
|
|
46
|
+
Searches for a song by artist and title.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
artist : str
|
|
51
|
+
The name of the artist.
|
|
52
|
+
song : str
|
|
53
|
+
The title of the song.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
Optional[MusicInfo]
|
|
58
|
+
The music information if found, otherwise None.
|
|
59
|
+
"""
|
|
60
|
+
if not is_valid_string(artist) or not is_valid_string(song):
|
|
61
|
+
raise InvalidValueException(
|
|
62
|
+
"Artist and song names must be valid strings and can't be empty."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
search_types = ["track", "album"]
|
|
66
|
+
|
|
67
|
+
for search_type in search_types:
|
|
68
|
+
endpoint = f"{self.api_url}/search/{search_type}"
|
|
69
|
+
query = f'?q=artist:"{artist}" {search_type}:"{song}"&limit=10'
|
|
70
|
+
query_url = endpoint + query
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
response = self._session.get(query_url, timeout=30)
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
except requests.RequestException as e:
|
|
76
|
+
raise NetworkException(f"Network error occurred: {e}")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
raise DeezerException(f"An error occurred while searching Deezer: {e}")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
result = response.json()["data"]
|
|
82
|
+
except (IndexError, KeyError, ValueError) as e:
|
|
83
|
+
raise InvalidResponseException(f"Invalid response received: {e}")
|
|
84
|
+
|
|
85
|
+
music_info = self._parse_results(artist, song, result)
|
|
86
|
+
if music_info:
|
|
87
|
+
return music_info
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def _get_upc_isrc(self, music_id: int, music_type: str) -> Optional[Dict]:
|
|
92
|
+
"""
|
|
93
|
+
Retrieves UPC and ISRC information for a given music ID and type.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
music_id : int
|
|
98
|
+
The ID of the music.
|
|
99
|
+
music_type : str
|
|
100
|
+
The type of the music (track or album).
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
Optional[Dict]
|
|
105
|
+
A dictionary containing UPC and ISRC information.
|
|
106
|
+
"""
|
|
107
|
+
match music_type:
|
|
108
|
+
case "track":
|
|
109
|
+
return self._get_track_info(music_id)
|
|
110
|
+
case "album":
|
|
111
|
+
return self._get_album_info(music_id)
|
|
112
|
+
case _:
|
|
113
|
+
raise DeezerException(f"Invalid music type: {music_type}")
|
|
114
|
+
|
|
115
|
+
def _get_track_info(self, music_id: int) -> Optional[Dict]:
|
|
116
|
+
"""
|
|
117
|
+
Retrieves track information for a given track ID.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
music_id : int
|
|
122
|
+
The ID of the track.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
Optional[Dict]
|
|
127
|
+
A dictionary containing track information.
|
|
128
|
+
"""
|
|
129
|
+
query_url = f"{self.api_url}/track/{music_id}"
|
|
130
|
+
try:
|
|
131
|
+
response = self._session.get(query_url, timeout=30)
|
|
132
|
+
response.raise_for_status()
|
|
133
|
+
except requests.RequestException as e:
|
|
134
|
+
raise NetworkException(f"Network error occurred: {e}")
|
|
135
|
+
except Exception as e:
|
|
136
|
+
raise DeezerException(f"An error occurred while fetching track info: {e}")
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
result = response.json()
|
|
140
|
+
except ValueError as e:
|
|
141
|
+
raise InvalidResponseException(f"Invalid response received: {e}")
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
"isrc": result.get("isrc"),
|
|
145
|
+
"release_date": result.get("release_date"),
|
|
146
|
+
"tempo": result.get("bpm"),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
def _get_album_info(self, music_id: int) -> Optional[Dict]:
|
|
150
|
+
"""
|
|
151
|
+
Retrieves album information for a given album ID.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
music_id : int
|
|
156
|
+
The ID of the album.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
Optional[Dict]
|
|
161
|
+
A dictionary containing album information.
|
|
162
|
+
"""
|
|
163
|
+
query_url = f"{self.api_url}/album/{music_id}"
|
|
164
|
+
try:
|
|
165
|
+
response = self._session.get(query_url, timeout=30)
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
except requests.RequestException as e:
|
|
168
|
+
raise NetworkException(f"Network error occurred: {e}")
|
|
169
|
+
except Exception as e:
|
|
170
|
+
raise DeezerException(f"An error occurred while fetching album info: {e}")
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
result = response.json()
|
|
174
|
+
except ValueError as e:
|
|
175
|
+
raise InvalidResponseException(f"Invalid response received: {e}")
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
"genre": (
|
|
179
|
+
result["genres"]["data"][0]["name"]
|
|
180
|
+
if result["genres"]["data"]
|
|
181
|
+
else None
|
|
182
|
+
),
|
|
183
|
+
"release_date": result.get("release_date"),
|
|
184
|
+
"upc": result.get("upc"),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
def _parse_results(
|
|
188
|
+
self, artist: str, song: str, results: List[Dict]
|
|
189
|
+
) -> Optional[MusicInfo]:
|
|
190
|
+
"""
|
|
191
|
+
Parses the search results to find a matching song.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
artist : str
|
|
196
|
+
The name of the artist.
|
|
197
|
+
song : str
|
|
198
|
+
The title of the song.
|
|
199
|
+
results : List[Dict]
|
|
200
|
+
The search results from the API.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
Optional[MusicInfo]
|
|
205
|
+
The music information if a match is found, otherwise None.
|
|
206
|
+
"""
|
|
207
|
+
for result in results:
|
|
208
|
+
if not (
|
|
209
|
+
are_strings_similar(result["title"], song)
|
|
210
|
+
and are_strings_similar(result["artist"]["name"], artist)
|
|
211
|
+
):
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
return self._extract_music_info(result)
|
|
215
|
+
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def _extract_music_info(self, result: Dict) -> MusicInfo:
|
|
219
|
+
"""
|
|
220
|
+
Extracts music information from a search result.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
result : Dict
|
|
225
|
+
A single search result from the API.
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
MusicInfo
|
|
230
|
+
The extracted music information.
|
|
231
|
+
"""
|
|
232
|
+
music_type = result["type"]
|
|
233
|
+
music_info = MusicInfo(
|
|
234
|
+
album_art=(
|
|
235
|
+
result["album"]["cover_xl"]
|
|
236
|
+
if music_type == "track"
|
|
237
|
+
else result["cover_xl"]
|
|
238
|
+
),
|
|
239
|
+
album_title=(
|
|
240
|
+
result["album"]["title"] if music_type == "track" else result["title"]
|
|
241
|
+
),
|
|
242
|
+
album_type=result.get("record_type", music_type),
|
|
243
|
+
artists=result["artist"]["name"],
|
|
244
|
+
genre=None,
|
|
245
|
+
id=result["id"],
|
|
246
|
+
isrc=None,
|
|
247
|
+
lyrics=None,
|
|
248
|
+
release_date=None,
|
|
249
|
+
tempo=None,
|
|
250
|
+
title=result["title"],
|
|
251
|
+
type=music_type,
|
|
252
|
+
upc=None,
|
|
253
|
+
url=result["link"],
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if music_type == "track":
|
|
257
|
+
track_info = self._get_upc_isrc(result["id"], music_type)
|
|
258
|
+
music_info.isrc = track_info.get("isrc")
|
|
259
|
+
music_info.release_date = track_info.get("release_date")
|
|
260
|
+
music_info.tempo = track_info.get("tempo")
|
|
261
|
+
else:
|
|
262
|
+
album_info = self._get_upc_isrc(result["id"], music_type)
|
|
263
|
+
music_info.upc = album_info.get("upc")
|
|
264
|
+
music_info.release_date = album_info.get("release_date")
|
|
265
|
+
music_info.genre = album_info.get("genre")
|
|
266
|
+
|
|
267
|
+
return music_info
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if __name__ == "__main__":
|
|
271
|
+
deezer = Deezer()
|
|
272
|
+
try:
|
|
273
|
+
artist_name = input("Artist Name: ")
|
|
274
|
+
song_name = input("Song Name: ")
|
|
275
|
+
pprint(deezer.search(artist_name, song_name))
|
|
276
|
+
finally:
|
|
277
|
+
deezer._close_session()
|
yutipy/exceptions.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class YutipyException(Exception):
|
|
2
|
+
"""Base class for exceptions in the Yutipy package."""
|
|
3
|
+
|
|
4
|
+
pass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InvalidValueException(YutipyException):
|
|
8
|
+
"""Exception raised for invalid values."""
|
|
9
|
+
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DeezerException(YutipyException):
|
|
14
|
+
"""Exception raised for errors related to the Deezer API."""
|
|
15
|
+
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ItunesException(YutipyException):
|
|
20
|
+
"""Exception raised for errors related to the iTunes API."""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpotifyException(YutipyException):
|
|
26
|
+
"""Exception raised for errors related to the Spotify API."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MusicYTException(YutipyException):
|
|
32
|
+
"""Exception raised for errors related to the YouTube Music API."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AuthenticationException(YutipyException):
|
|
38
|
+
"""Exception raised for authentication errors."""
|
|
39
|
+
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class NetworkException(YutipyException):
|
|
44
|
+
"""Exception raised for network-related errors."""
|
|
45
|
+
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InvalidResponseException(YutipyException):
|
|
50
|
+
"""Exception raised for invalid responses from APIs."""
|
|
51
|
+
|
|
52
|
+
pass
|
yutipy/itunes.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pprint import pprint
|
|
3
|
+
from typing import Optional, Dict
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from yutipy.exceptions import (
|
|
8
|
+
InvalidResponseException,
|
|
9
|
+
InvalidValueException,
|
|
10
|
+
NetworkException,
|
|
11
|
+
ItunesException,
|
|
12
|
+
)
|
|
13
|
+
from yutipy.models import MusicInfo
|
|
14
|
+
from yutipy.utils.cheap_utils import are_strings_similar, is_valid_string
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Itunes:
|
|
18
|
+
"""A class to interact with the iTunes API."""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
"""Initializes the iTunes class and sets up the session."""
|
|
22
|
+
self._session = requests.Session()
|
|
23
|
+
self.api_url = "https://itunes.apple.com"
|
|
24
|
+
self._is_session_closed = False
|
|
25
|
+
|
|
26
|
+
def __enter__(self) -> "Itunes":
|
|
27
|
+
"""Enters the runtime context related to this object."""
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
|
31
|
+
"""Exits the runtime context related to this object."""
|
|
32
|
+
self._close_session()
|
|
33
|
+
|
|
34
|
+
def _close_session(self) -> None:
|
|
35
|
+
"""Closes the current session."""
|
|
36
|
+
if not self.is_session_closed:
|
|
37
|
+
self._session.close()
|
|
38
|
+
self._is_session_closed = True
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_session_closed(self) -> bool:
|
|
42
|
+
"""Checks if the session is closed."""
|
|
43
|
+
return self._is_session_closed
|
|
44
|
+
|
|
45
|
+
def search(self, artist: str, song: str) -> Optional[MusicInfo]:
|
|
46
|
+
"""
|
|
47
|
+
Searches for a song by artist and title.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
artist : str
|
|
52
|
+
The name of the artist.
|
|
53
|
+
song : str
|
|
54
|
+
The title of the song.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
Optional[MusicInfo]
|
|
59
|
+
The music information if found, otherwise None.
|
|
60
|
+
"""
|
|
61
|
+
if not is_valid_string(artist) or not is_valid_string(song):
|
|
62
|
+
raise InvalidValueException(
|
|
63
|
+
"Artist and song names must be valid strings and can't be empty."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
entities = ["song", "album"]
|
|
67
|
+
for entity in entities:
|
|
68
|
+
endpoint = f"{self.api_url}/search"
|
|
69
|
+
query = f"?term={artist} - {song}&media=music&entity={entity}&limit=10"
|
|
70
|
+
query_url = endpoint + query
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
response = self._session.get(query_url, timeout=30)
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
except requests.RequestException as e:
|
|
76
|
+
raise NetworkException(f"Network error occurred: {e}")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
raise ItunesException(f"An error occurred while searching iTunes: {e}")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
result = response.json()["results"]
|
|
82
|
+
except (IndexError, KeyError, ValueError) as e:
|
|
83
|
+
raise InvalidResponseException(f"Invalid response received: {e}")
|
|
84
|
+
|
|
85
|
+
music_info = self._parse_result(artist, song, result)
|
|
86
|
+
if music_info:
|
|
87
|
+
return music_info
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def _parse_result(
|
|
92
|
+
self, artist: str, song: str, results: list[dict]
|
|
93
|
+
) -> Optional[MusicInfo]:
|
|
94
|
+
"""
|
|
95
|
+
Parses the search results to find a matching song.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
artist : str
|
|
100
|
+
The name of the artist.
|
|
101
|
+
song : str
|
|
102
|
+
The title of the song.
|
|
103
|
+
results : list
|
|
104
|
+
The search results from the API.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
Optional[MusicInfo]
|
|
109
|
+
The music information if a match is found, otherwise None.
|
|
110
|
+
"""
|
|
111
|
+
for result in results:
|
|
112
|
+
if not (
|
|
113
|
+
are_strings_similar(
|
|
114
|
+
result.get("trackName", result["collectionName"]), song
|
|
115
|
+
)
|
|
116
|
+
and are_strings_similar(result["artistName"], artist)
|
|
117
|
+
):
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
album_title, album_type = self._extract_album_info(result)
|
|
121
|
+
release_date = self._format_release_date(result["releaseDate"])
|
|
122
|
+
|
|
123
|
+
return MusicInfo(
|
|
124
|
+
album_art=result["artworkUrl100"],
|
|
125
|
+
album_title=album_title,
|
|
126
|
+
album_type=album_type.lower(),
|
|
127
|
+
artists=result["artistName"],
|
|
128
|
+
genre=result["primaryGenreName"],
|
|
129
|
+
id=result.get("trackId", result["collectionId"]),
|
|
130
|
+
isrc=None,
|
|
131
|
+
lyrics=None,
|
|
132
|
+
release_date=release_date,
|
|
133
|
+
tempo=None,
|
|
134
|
+
title=result.get("trackName", album_title),
|
|
135
|
+
type=result["wrapperType"],
|
|
136
|
+
upc=None,
|
|
137
|
+
url=result.get("trackViewUrl", result["collectionViewUrl"]),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def _extract_album_info(self, result: dict) -> tuple:
|
|
143
|
+
"""
|
|
144
|
+
Extracts album information from a search result.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
result : dict
|
|
149
|
+
A single search result from the API.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
tuple
|
|
154
|
+
The extracted album title and type.
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
album_title, album_type = result["collectionName"].split("-")
|
|
158
|
+
return album_title.strip(), album_type.strip()
|
|
159
|
+
except ValueError:
|
|
160
|
+
return result["collectionName"], result["wrapperType"]
|
|
161
|
+
|
|
162
|
+
def _format_release_date(self, release_date: str) -> str:
|
|
163
|
+
"""
|
|
164
|
+
Formats the release date to a standard format.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
release_date : str
|
|
169
|
+
The release date from the API.
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
str
|
|
174
|
+
The formatted release date.
|
|
175
|
+
"""
|
|
176
|
+
return datetime.strptime(release_date, "%Y-%m-%dT%H:%M:%SZ").strftime(
|
|
177
|
+
"%Y-%m-%d"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
itunes = Itunes()
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
artist_name = input("Artist Name: ")
|
|
186
|
+
song_name = input("Song Name: ")
|
|
187
|
+
pprint(itunes.search(artist_name, song_name))
|
|
188
|
+
finally:
|
|
189
|
+
itunes._close_session()
|
yutipy/models.py
ADDED
|
@@ -0,0 +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
|