yutipy 1.3.1__py3-none-any.whl → 1.4.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/deezer.py +30 -4
- yutipy/itunes.py +27 -4
- yutipy/kkbox.py +46 -6
- yutipy/musicyt.py +42 -5
- yutipy/spotify.py +60 -11
- yutipy/utils/cheap_utils.py +34 -5
- yutipy/utils/logger.py +4 -0
- yutipy/yutipy_music.py +71 -13
- {yutipy-1.3.1.dist-info → yutipy-1.4.0.dist-info}/METADATA +1 -1
- yutipy-1.4.0.dist-info/RECORD +17 -0
- yutipy-1.3.1.dist-info/RECORD +0 -16
- {yutipy-1.3.1.dist-info → yutipy-1.4.0.dist-info}/WHEEL +0 -0
- {yutipy-1.3.1.dist-info → yutipy-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {yutipy-1.3.1.dist-info → yutipy-1.4.0.dist-info}/top_level.txt +0 -0
yutipy/deezer.py
CHANGED
|
@@ -21,6 +21,8 @@ class Deezer:
|
|
|
21
21
|
self._session = requests.Session()
|
|
22
22
|
self.api_url = "https://api.deezer.com"
|
|
23
23
|
self._is_session_closed = False
|
|
24
|
+
self.normalize_non_english = True
|
|
25
|
+
self._translation_session = requests.Session()
|
|
24
26
|
|
|
25
27
|
def __enter__(self) -> "Deezer":
|
|
26
28
|
"""Enters the runtime context related to this object."""
|
|
@@ -34,6 +36,7 @@ class Deezer:
|
|
|
34
36
|
"""Closes the current session."""
|
|
35
37
|
if not self.is_session_closed:
|
|
36
38
|
self._session.close()
|
|
39
|
+
self._translation_session.close()
|
|
37
40
|
self._is_session_closed = True
|
|
38
41
|
|
|
39
42
|
@property
|
|
@@ -41,7 +44,13 @@ class Deezer:
|
|
|
41
44
|
"""Checks if the session is closed."""
|
|
42
45
|
return self._is_session_closed
|
|
43
46
|
|
|
44
|
-
def search(
|
|
47
|
+
def search(
|
|
48
|
+
self,
|
|
49
|
+
artist: str,
|
|
50
|
+
song: str,
|
|
51
|
+
limit: int = 10,
|
|
52
|
+
normalize_non_english: bool = True,
|
|
53
|
+
) -> Optional[MusicInfo]:
|
|
45
54
|
"""
|
|
46
55
|
Searches for a song by artist and title.
|
|
47
56
|
|
|
@@ -51,6 +60,11 @@ class Deezer:
|
|
|
51
60
|
The name of the artist.
|
|
52
61
|
song : str
|
|
53
62
|
The title of the song.
|
|
63
|
+
limit: int, optional
|
|
64
|
+
The number of items to retrieve from API. ``limit >=1 and <= 50``. Default is ``10``.
|
|
65
|
+
normalize_non_english : bool, optional
|
|
66
|
+
Whether to normalize non-English characters for comparison. Default is ``True``.
|
|
67
|
+
|
|
54
68
|
|
|
55
69
|
Returns
|
|
56
70
|
-------
|
|
@@ -62,11 +76,13 @@ class Deezer:
|
|
|
62
76
|
"Artist and song names must be valid strings and can't be empty."
|
|
63
77
|
)
|
|
64
78
|
|
|
79
|
+
self.normalize_non_english = normalize_non_english
|
|
80
|
+
|
|
65
81
|
search_types = ["track", "album"]
|
|
66
82
|
|
|
67
83
|
for search_type in search_types:
|
|
68
84
|
endpoint = f"{self.api_url}/search/{search_type}"
|
|
69
|
-
query = f'?q=artist:"{artist}" {search_type}:"{song}"&limit=
|
|
85
|
+
query = f'?q=artist:"{artist}" {search_type}:"{song}"&limit={limit}'
|
|
70
86
|
query_url = endpoint + query
|
|
71
87
|
|
|
72
88
|
try:
|
|
@@ -205,8 +221,18 @@ class Deezer:
|
|
|
205
221
|
"""
|
|
206
222
|
for result in results:
|
|
207
223
|
if not (
|
|
208
|
-
are_strings_similar(
|
|
209
|
-
|
|
224
|
+
are_strings_similar(
|
|
225
|
+
result["title"],
|
|
226
|
+
song,
|
|
227
|
+
use_translation=self.normalize_non_english,
|
|
228
|
+
translation_session=self._translation_session,
|
|
229
|
+
)
|
|
230
|
+
and are_strings_similar(
|
|
231
|
+
result["artist"]["name"],
|
|
232
|
+
artist,
|
|
233
|
+
use_translation=self.normalize_non_english,
|
|
234
|
+
translation_session=self._translation_session,
|
|
235
|
+
)
|
|
210
236
|
):
|
|
211
237
|
continue
|
|
212
238
|
|
yutipy/itunes.py
CHANGED
|
@@ -22,6 +22,8 @@ class Itunes:
|
|
|
22
22
|
self._session = requests.Session()
|
|
23
23
|
self.api_url = "https://itunes.apple.com"
|
|
24
24
|
self._is_session_closed = False
|
|
25
|
+
self.normalize_non_english = True
|
|
26
|
+
self._translation_session = requests.Session()
|
|
25
27
|
|
|
26
28
|
def __enter__(self) -> "Itunes":
|
|
27
29
|
"""Enters the runtime context related to this object."""
|
|
@@ -35,6 +37,7 @@ class Itunes:
|
|
|
35
37
|
"""Closes the current session."""
|
|
36
38
|
if not self.is_session_closed:
|
|
37
39
|
self._session.close()
|
|
40
|
+
self._translation_session.close()
|
|
38
41
|
self._is_session_closed = True
|
|
39
42
|
|
|
40
43
|
@property
|
|
@@ -42,7 +45,13 @@ class Itunes:
|
|
|
42
45
|
"""Checks if the session is closed."""
|
|
43
46
|
return self._is_session_closed
|
|
44
47
|
|
|
45
|
-
def search(
|
|
48
|
+
def search(
|
|
49
|
+
self,
|
|
50
|
+
artist: str,
|
|
51
|
+
song: str,
|
|
52
|
+
limit: int = 10,
|
|
53
|
+
normalize_non_english: bool = True,
|
|
54
|
+
) -> Optional[MusicInfo]:
|
|
46
55
|
"""
|
|
47
56
|
Searches for a song by artist and title.
|
|
48
57
|
|
|
@@ -52,6 +61,10 @@ class Itunes:
|
|
|
52
61
|
The name of the artist.
|
|
53
62
|
song : str
|
|
54
63
|
The title of the song.
|
|
64
|
+
limit: int, optional
|
|
65
|
+
The number of items to retrieve from API. ``limit >=1 and <= 50``. Default is ``10``.
|
|
66
|
+
normalize_non_english : bool, optional
|
|
67
|
+
Whether to normalize non-English characters for comparison. Default is ``True``.
|
|
55
68
|
|
|
56
69
|
Returns
|
|
57
70
|
-------
|
|
@@ -63,10 +76,12 @@ class Itunes:
|
|
|
63
76
|
"Artist and song names must be valid strings and can't be empty."
|
|
64
77
|
)
|
|
65
78
|
|
|
79
|
+
self.normalize_non_english = normalize_non_english
|
|
80
|
+
|
|
66
81
|
entities = ["song", "album"]
|
|
67
82
|
for entity in entities:
|
|
68
83
|
endpoint = f"{self.api_url}/search"
|
|
69
|
-
query = f"?term={artist} - {song}&media=music&entity={entity}&limit=
|
|
84
|
+
query = f"?term={artist} - {song}&media=music&entity={entity}&limit={limit}"
|
|
70
85
|
query_url = endpoint + query
|
|
71
86
|
|
|
72
87
|
try:
|
|
@@ -111,9 +126,17 @@ class Itunes:
|
|
|
111
126
|
for result in results:
|
|
112
127
|
if not (
|
|
113
128
|
are_strings_similar(
|
|
114
|
-
result.get("trackName", result["collectionName"]),
|
|
129
|
+
result.get("trackName", result["collectionName"]),
|
|
130
|
+
song,
|
|
131
|
+
use_translation=self.normalize_non_english,
|
|
132
|
+
translation_session=self._translation_session,
|
|
133
|
+
)
|
|
134
|
+
and are_strings_similar(
|
|
135
|
+
result["artistName"],
|
|
136
|
+
artist,
|
|
137
|
+
use_translation=self.normalize_non_english,
|
|
138
|
+
translation_session=self._translation_session,
|
|
115
139
|
)
|
|
116
|
-
and are_strings_similar(result["artistName"], artist)
|
|
117
140
|
):
|
|
118
141
|
continue
|
|
119
142
|
|
yutipy/kkbox.py
CHANGED
|
@@ -57,6 +57,8 @@ class KKBox:
|
|
|
57
57
|
self.__start_time = time.time()
|
|
58
58
|
self._is_session_closed = False
|
|
59
59
|
self.valid_territories = ["HK", "JP", "MY", "SG", "TW"]
|
|
60
|
+
self.normalize_non_english = True
|
|
61
|
+
self._translation_session = requests.Session()
|
|
60
62
|
|
|
61
63
|
def __enter__(self):
|
|
62
64
|
"""Enters the runtime context related to this object."""
|
|
@@ -70,6 +72,7 @@ class KKBox:
|
|
|
70
72
|
"""Closes the current session."""
|
|
71
73
|
if not self.is_session_closed:
|
|
72
74
|
self._session.close()
|
|
75
|
+
self._translation_session.close()
|
|
73
76
|
self._is_session_closed = True
|
|
74
77
|
|
|
75
78
|
@property
|
|
@@ -134,7 +137,12 @@ class KKBox:
|
|
|
134
137
|
self.__start_time = time.time()
|
|
135
138
|
|
|
136
139
|
def search(
|
|
137
|
-
self,
|
|
140
|
+
self,
|
|
141
|
+
artist: str,
|
|
142
|
+
song: str,
|
|
143
|
+
territory: str = "TW",
|
|
144
|
+
limit: int = 10,
|
|
145
|
+
normalize_non_english: bool = True,
|
|
138
146
|
) -> Optional[MusicInfo]:
|
|
139
147
|
"""
|
|
140
148
|
Searches for a song by artist and title.
|
|
@@ -148,6 +156,10 @@ class KKBox:
|
|
|
148
156
|
territory : str
|
|
149
157
|
Two-letter country codes from ISO 3166-1 alpha-2.
|
|
150
158
|
Allowed values: ``HK``, ``JP``, ``MY``, ``SG``, ``TW``.
|
|
159
|
+
limit: int, optional
|
|
160
|
+
The number of items to retrieve from API. ``limit >=1 and <= 50``. Default is ``10``.
|
|
161
|
+
normalize_non_english : bool, optional
|
|
162
|
+
Whether to normalize non-English characters for comparison. Default is ``True``.
|
|
151
163
|
|
|
152
164
|
Returns
|
|
153
165
|
-------
|
|
@@ -159,9 +171,13 @@ class KKBox:
|
|
|
159
171
|
"Artist and song names must be valid strings and can't be empty."
|
|
160
172
|
)
|
|
161
173
|
|
|
174
|
+
self.normalize_non_english = normalize_non_english
|
|
175
|
+
|
|
162
176
|
self.__refresh_token_if_expired()
|
|
163
177
|
|
|
164
|
-
query =
|
|
178
|
+
query = (
|
|
179
|
+
f"?q={artist} - {song}&type=track,album&territory={territory}&limit={limit}"
|
|
180
|
+
)
|
|
165
181
|
query_url = f"{self.api_url}/search{query}"
|
|
166
182
|
|
|
167
183
|
try:
|
|
@@ -285,12 +301,24 @@ class KKBox:
|
|
|
285
301
|
Optional[MusicInfo]
|
|
286
302
|
The music information if found, otherwise None.
|
|
287
303
|
"""
|
|
288
|
-
if not are_strings_similar(
|
|
304
|
+
if not are_strings_similar(
|
|
305
|
+
track["name"],
|
|
306
|
+
song,
|
|
307
|
+
use_translation=self.normalize_non_english,
|
|
308
|
+
translation_session=self._translation_session,
|
|
309
|
+
):
|
|
289
310
|
return None
|
|
290
311
|
|
|
291
312
|
artists_name = track["album"]["artist"]["name"]
|
|
292
313
|
matching_artists = (
|
|
293
|
-
artists_name
|
|
314
|
+
artists_name
|
|
315
|
+
if are_strings_similar(
|
|
316
|
+
artists_name,
|
|
317
|
+
artist,
|
|
318
|
+
use_translation=self.normalize_non_english,
|
|
319
|
+
translation_session=self._translation_session,
|
|
320
|
+
)
|
|
321
|
+
else None
|
|
294
322
|
)
|
|
295
323
|
|
|
296
324
|
if matching_artists:
|
|
@@ -333,12 +361,24 @@ class KKBox:
|
|
|
333
361
|
Optional[MusicInfo]
|
|
334
362
|
The music information if found, otherwise None.
|
|
335
363
|
"""
|
|
336
|
-
if not are_strings_similar(
|
|
364
|
+
if not are_strings_similar(
|
|
365
|
+
album["name"],
|
|
366
|
+
song,
|
|
367
|
+
use_translation=self.normalize_non_english,
|
|
368
|
+
translation_session=self._translation_session,
|
|
369
|
+
):
|
|
337
370
|
return None
|
|
338
371
|
|
|
339
372
|
artists_name = album["artist"]["name"]
|
|
340
373
|
matching_artists = (
|
|
341
|
-
artists_name
|
|
374
|
+
artists_name
|
|
375
|
+
if are_strings_similar(
|
|
376
|
+
artists_name,
|
|
377
|
+
artist,
|
|
378
|
+
use_translation=self.normalize_non_english,
|
|
379
|
+
translation_session=self._translation_session,
|
|
380
|
+
)
|
|
381
|
+
else None
|
|
342
382
|
)
|
|
343
383
|
|
|
344
384
|
if matching_artists:
|
yutipy/musicyt.py
CHANGED
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
from pprint import pprint
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
+
import requests
|
|
5
6
|
from ytmusicapi import YTMusic, exceptions
|
|
6
7
|
|
|
7
8
|
from yutipy.exceptions import (
|
|
@@ -19,8 +20,28 @@ class MusicYT:
|
|
|
19
20
|
def __init__(self) -> None:
|
|
20
21
|
"""Initializes the YouTube Music class and sets up the session."""
|
|
21
22
|
self.ytmusic = YTMusic()
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
self._is_session_closed = False
|
|
24
|
+
self.normalize_non_english = True
|
|
25
|
+
self._translation_session = requests.Session()
|
|
26
|
+
|
|
27
|
+
def close_session(self) -> None:
|
|
28
|
+
"""Closes the current session(s)."""
|
|
29
|
+
if not self.is_session_closed:
|
|
30
|
+
self._translation_session.close()
|
|
31
|
+
self._is_session_closed = True
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def is_session_closed(self) -> bool:
|
|
35
|
+
"""Checks if the session is closed."""
|
|
36
|
+
return self._is_session_closed
|
|
37
|
+
|
|
38
|
+
def search(
|
|
39
|
+
self,
|
|
40
|
+
artist: str,
|
|
41
|
+
song: str,
|
|
42
|
+
limit: int = 10,
|
|
43
|
+
normalize_non_english: bool = True,
|
|
44
|
+
) -> Optional[MusicInfo]:
|
|
24
45
|
"""
|
|
25
46
|
Searches for a song by artist and title.
|
|
26
47
|
|
|
@@ -30,6 +51,10 @@ class MusicYT:
|
|
|
30
51
|
The name of the artist.
|
|
31
52
|
song : str
|
|
32
53
|
The title of the song.
|
|
54
|
+
limit: int, optional
|
|
55
|
+
The number of items to retrieve from API. ``limit >=1 and <= 50``. Default is ``10``.
|
|
56
|
+
normalize_non_english : bool, optional
|
|
57
|
+
Whether to normalize non-English characters for comparison. Default is ``True``.
|
|
33
58
|
|
|
34
59
|
Returns
|
|
35
60
|
-------
|
|
@@ -41,10 +66,12 @@ class MusicYT:
|
|
|
41
66
|
"Artist and song names must be valid strings and can't be empty."
|
|
42
67
|
)
|
|
43
68
|
|
|
69
|
+
self.normalize_non_english = normalize_non_english
|
|
70
|
+
|
|
44
71
|
query = f"{artist} - {song}"
|
|
45
72
|
|
|
46
73
|
try:
|
|
47
|
-
results = self.ytmusic.search(query=query)
|
|
74
|
+
results = self.ytmusic.search(query=query, limit=limit)
|
|
48
75
|
except exceptions.YTMusicServerError as e:
|
|
49
76
|
raise NetworkException(f"Network error occurred: {e}")
|
|
50
77
|
|
|
@@ -76,8 +103,18 @@ class MusicYT:
|
|
|
76
103
|
return False
|
|
77
104
|
|
|
78
105
|
return any(
|
|
79
|
-
are_strings_similar(
|
|
80
|
-
|
|
106
|
+
are_strings_similar(
|
|
107
|
+
result.get("title"),
|
|
108
|
+
song,
|
|
109
|
+
use_translation=self.normalize_non_english,
|
|
110
|
+
translation_session=self._translation_session,
|
|
111
|
+
)
|
|
112
|
+
and are_strings_similar(
|
|
113
|
+
_artist.get("name"),
|
|
114
|
+
artist,
|
|
115
|
+
use_translation=self.normalize_non_english,
|
|
116
|
+
translation_session=self._translation_session,
|
|
117
|
+
)
|
|
81
118
|
for _artist in result.get("artists", [])
|
|
82
119
|
)
|
|
83
120
|
|
yutipy/spotify.py
CHANGED
|
@@ -62,6 +62,8 @@ class Spotify:
|
|
|
62
62
|
self.__header, self.__expires_in = self.__authenticate()
|
|
63
63
|
self.__start_time = time.time()
|
|
64
64
|
self._is_session_closed = False
|
|
65
|
+
self.normalize_non_english = True
|
|
66
|
+
self._translation_session = requests.Session()
|
|
65
67
|
|
|
66
68
|
def __enter__(self):
|
|
67
69
|
"""Enters the runtime context related to this object."""
|
|
@@ -72,9 +74,10 @@ class Spotify:
|
|
|
72
74
|
self.close_session()
|
|
73
75
|
|
|
74
76
|
def close_session(self) -> None:
|
|
75
|
-
"""Closes the current session."""
|
|
77
|
+
"""Closes the current session(s)."""
|
|
76
78
|
if not self.is_session_closed:
|
|
77
79
|
self._session.close()
|
|
80
|
+
self._translation_session.close()
|
|
78
81
|
self._is_session_closed = True
|
|
79
82
|
|
|
80
83
|
@property
|
|
@@ -138,7 +141,13 @@ class Spotify:
|
|
|
138
141
|
self.__header, self.__expires_in = self.__authenticate()
|
|
139
142
|
self.__start_time = time.time()
|
|
140
143
|
|
|
141
|
-
def search(
|
|
144
|
+
def search(
|
|
145
|
+
self,
|
|
146
|
+
artist: str,
|
|
147
|
+
song: str,
|
|
148
|
+
limit: int = 10,
|
|
149
|
+
normalize_non_english: bool = True,
|
|
150
|
+
) -> Optional[MusicInfo]:
|
|
142
151
|
"""
|
|
143
152
|
Searches for a song by artist and title.
|
|
144
153
|
|
|
@@ -148,6 +157,10 @@ class Spotify:
|
|
|
148
157
|
The name of the artist.
|
|
149
158
|
song : str
|
|
150
159
|
The title of the song.
|
|
160
|
+
limit: int, optional
|
|
161
|
+
The number of items to retrieve from API. ``limit >=1 and <= 50``. Default is ``10``.
|
|
162
|
+
normalize_non_english : bool, optional
|
|
163
|
+
Whether to normalize non-English characters for comparison. Default is ``True``.
|
|
151
164
|
|
|
152
165
|
Returns
|
|
153
166
|
-------
|
|
@@ -159,10 +172,12 @@ class Spotify:
|
|
|
159
172
|
"Artist and song names must be valid strings and can't be empty."
|
|
160
173
|
)
|
|
161
174
|
|
|
175
|
+
self.normalize_non_english = normalize_non_english
|
|
176
|
+
|
|
162
177
|
music_info = None
|
|
163
178
|
queries = [
|
|
164
|
-
f"?q=artist:{artist} track:{song}&type=track&limit=
|
|
165
|
-
f"?q=artist:{artist} album:{song}&type=album&limit=
|
|
179
|
+
f"?q=artist:{artist} track:{song}&type=track&limit={limit}",
|
|
180
|
+
f"?q=artist:{artist} album:{song}&type=album&limit={limit}",
|
|
166
181
|
]
|
|
167
182
|
|
|
168
183
|
for query in queries:
|
|
@@ -192,7 +207,13 @@ class Spotify:
|
|
|
192
207
|
return music_info
|
|
193
208
|
|
|
194
209
|
def search_advanced(
|
|
195
|
-
self,
|
|
210
|
+
self,
|
|
211
|
+
artist: str,
|
|
212
|
+
song: str,
|
|
213
|
+
isrc: str = None,
|
|
214
|
+
upc: str = None,
|
|
215
|
+
limit: int = 1,
|
|
216
|
+
normalize_non_english: bool = True,
|
|
196
217
|
) -> Optional[MusicInfo]:
|
|
197
218
|
"""
|
|
198
219
|
Searches for a song by artist, title, ISRC, or UPC.
|
|
@@ -207,6 +228,10 @@ class Spotify:
|
|
|
207
228
|
The ISRC of the track.
|
|
208
229
|
upc : str, optional
|
|
209
230
|
The UPC of the album.
|
|
231
|
+
limit: int, optional
|
|
232
|
+
The number of items to retrieve from API. ``limit >=1 and <= 50``. Default is ``1``.
|
|
233
|
+
normalize_non_english : bool, optional
|
|
234
|
+
Whether to normalize non-English characters for comparison. Default is ``True``.
|
|
210
235
|
|
|
211
236
|
Returns
|
|
212
237
|
-------
|
|
@@ -218,12 +243,14 @@ class Spotify:
|
|
|
218
243
|
"Artist and song names must be valid strings and can't be empty."
|
|
219
244
|
)
|
|
220
245
|
|
|
246
|
+
self.normalize_non_english = normalize_non_english
|
|
247
|
+
|
|
221
248
|
self.__refresh_token_if_expired()
|
|
222
249
|
|
|
223
250
|
if isrc:
|
|
224
|
-
query = f"?q={artist} {song} isrc:{isrc}&type=track&limit=
|
|
251
|
+
query = f"?q={artist} {song} isrc:{isrc}&type=track&limit={limit}"
|
|
225
252
|
elif upc:
|
|
226
|
-
query = f"?q={artist} {song} upc:{upc}&type=album&limit=
|
|
253
|
+
query = f"?q={artist} {song} upc:{upc}&type=album&limit={limit}"
|
|
227
254
|
else:
|
|
228
255
|
raise InvalidValueException("ISRC or UPC must be provided.")
|
|
229
256
|
|
|
@@ -337,14 +364,25 @@ class Spotify:
|
|
|
337
364
|
Optional[MusicInfo]
|
|
338
365
|
The music information if found, otherwise None.
|
|
339
366
|
"""
|
|
340
|
-
if not are_strings_similar(
|
|
367
|
+
if not are_strings_similar(
|
|
368
|
+
track["name"],
|
|
369
|
+
song,
|
|
370
|
+
use_translation=self.normalize_non_english,
|
|
371
|
+
translation_session=self._translation_session,
|
|
372
|
+
):
|
|
341
373
|
return None
|
|
342
374
|
|
|
343
375
|
artists_name = [x["name"] for x in track["artists"]]
|
|
344
376
|
matching_artists = [
|
|
345
377
|
x["name"]
|
|
346
378
|
for x in track["artists"]
|
|
347
|
-
if are_strings_similar(
|
|
379
|
+
if are_strings_similar(
|
|
380
|
+
x["name"],
|
|
381
|
+
artist,
|
|
382
|
+
use_translation=self.normalize_non_english,
|
|
383
|
+
translation_session=self._translation_session,
|
|
384
|
+
)
|
|
385
|
+
or x["id"] in artist_ids
|
|
348
386
|
]
|
|
349
387
|
|
|
350
388
|
if matching_artists:
|
|
@@ -389,14 +427,25 @@ class Spotify:
|
|
|
389
427
|
Optional[MusicInfo]
|
|
390
428
|
The music information if found, otherwise None.
|
|
391
429
|
"""
|
|
392
|
-
if not are_strings_similar(
|
|
430
|
+
if not are_strings_similar(
|
|
431
|
+
album["name"],
|
|
432
|
+
song,
|
|
433
|
+
use_translation=self.normalize_non_english,
|
|
434
|
+
translation_session=self._translation_session,
|
|
435
|
+
):
|
|
393
436
|
return None
|
|
394
437
|
|
|
395
438
|
artists_name = [x["name"] for x in album["artists"]]
|
|
396
439
|
matching_artists = [
|
|
397
440
|
x["name"]
|
|
398
441
|
for x in album["artists"]
|
|
399
|
-
if are_strings_similar(
|
|
442
|
+
if are_strings_similar(
|
|
443
|
+
x["name"],
|
|
444
|
+
artist,
|
|
445
|
+
use_translation=self.normalize_non_english,
|
|
446
|
+
translation_session=self._translation_session,
|
|
447
|
+
)
|
|
448
|
+
or x["id"] in artist_ids
|
|
400
449
|
]
|
|
401
450
|
|
|
402
451
|
if matching_artists:
|
yutipy/utils/cheap_utils.py
CHANGED
|
@@ -7,6 +7,7 @@ def translate_text(
|
|
|
7
7
|
text: str,
|
|
8
8
|
sl: str = None,
|
|
9
9
|
dl: str = "en",
|
|
10
|
+
session: requests.Session = None,
|
|
10
11
|
) -> dict:
|
|
11
12
|
"""
|
|
12
13
|
Translate text from one language to another.
|
|
@@ -15,7 +16,8 @@ def translate_text(
|
|
|
15
16
|
text (str): The text to be translated.
|
|
16
17
|
sl (str, optional): The source language code (e.g., 'en' for English, 'es' for Spanish). If not provided, the API will attempt to detect the source language.
|
|
17
18
|
dl (str, optional): The destination language code (default is 'en' for English).
|
|
18
|
-
|
|
19
|
+
session (requests.Session, optional): A `requests.Session` object to use for making the API request. If not provided, a new session will be created and closed within the function.
|
|
20
|
+
Providing your own session can improve performance by reusing the same session for multiple requests. Don't forget to close the session afterwards.
|
|
19
21
|
|
|
20
22
|
Returns:
|
|
21
23
|
dict: A dictionary containing the following keys:
|
|
@@ -24,12 +26,17 @@ def translate_text(
|
|
|
24
26
|
- 'destination-text': The translated text.
|
|
25
27
|
- 'destination-language': The destination language code.
|
|
26
28
|
"""
|
|
29
|
+
default_session = False
|
|
30
|
+
if session is None:
|
|
31
|
+
default_session = True
|
|
32
|
+
session = requests.Session()
|
|
33
|
+
|
|
27
34
|
if sl:
|
|
28
35
|
url = f"https://ftapi.pythonanywhere.com/translate?sl={sl}&dl={dl}&text={text}"
|
|
29
36
|
else:
|
|
30
37
|
url = f"https://ftapi.pythonanywhere.com/translate?dl={dl}&text={text}"
|
|
31
38
|
|
|
32
|
-
response =
|
|
39
|
+
response = session.get(url)
|
|
33
40
|
response_json = response.json()
|
|
34
41
|
result = {
|
|
35
42
|
"source-text": response_json["source-text"],
|
|
@@ -37,10 +44,20 @@ def translate_text(
|
|
|
37
44
|
"destination-text": response_json["destination-text"],
|
|
38
45
|
"destination-language": response_json["destination-language"],
|
|
39
46
|
}
|
|
47
|
+
|
|
48
|
+
if default_session:
|
|
49
|
+
session.close()
|
|
50
|
+
|
|
40
51
|
return result
|
|
41
52
|
|
|
42
53
|
|
|
43
|
-
def are_strings_similar(
|
|
54
|
+
def are_strings_similar(
|
|
55
|
+
str1: str,
|
|
56
|
+
str2: str,
|
|
57
|
+
threshold: int = 80,
|
|
58
|
+
use_translation: bool = True,
|
|
59
|
+
translation_session: requests.Session = None,
|
|
60
|
+
) -> bool:
|
|
44
61
|
"""
|
|
45
62
|
Determine if two strings are similar based on a given threshold.
|
|
46
63
|
|
|
@@ -48,12 +65,24 @@ def are_strings_similar(str1: str, str2: str, threshold: int = 80) -> bool:
|
|
|
48
65
|
str1 (str): First string to compare.
|
|
49
66
|
str2 (str): Second string to compare.
|
|
50
67
|
threshold (int, optional): Similarity threshold. Defaults to 80.
|
|
68
|
+
use_translation (bool, optional): Use translations to compare strings. Defaults to ``True``
|
|
69
|
+
translation_session (requests.Session, optional): A `requests.Session` object to use for making the API request. If not provided, a new session will be created and closed within the function.
|
|
70
|
+
Providing your own session can improve performance by reusing the same session for multiple requests. Don't forget to close the session afterwards.
|
|
51
71
|
|
|
52
72
|
Returns:
|
|
53
73
|
bool: True if the strings are similar, otherwise False.
|
|
54
74
|
"""
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
if use_translation:
|
|
76
|
+
str1 = (
|
|
77
|
+
translate_text(str1, session=translation_session)["destination-text"]
|
|
78
|
+
if translation_session
|
|
79
|
+
else translate_text(str1)["destination-text"]
|
|
80
|
+
)
|
|
81
|
+
str2 = (
|
|
82
|
+
translate_text(str2, session=translation_session)["destination-text"]
|
|
83
|
+
if translation_session
|
|
84
|
+
else translate_text(str2)["destination-text"]
|
|
85
|
+
)
|
|
57
86
|
|
|
58
87
|
similarity_score = fuzz.WRatio(str1, str2, processor=default_process)
|
|
59
88
|
return similarity_score > threshold
|
yutipy/utils/logger.py
ADDED
yutipy/yutipy_music.py
CHANGED
|
@@ -2,14 +2,17 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
2
2
|
from pprint import pprint
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
+
import requests
|
|
6
|
+
|
|
5
7
|
from yutipy.deezer import Deezer
|
|
6
|
-
from yutipy.exceptions import InvalidValueException
|
|
8
|
+
from yutipy.exceptions import InvalidValueException, KKBoxException, SpotifyException
|
|
7
9
|
from yutipy.itunes import Itunes
|
|
8
10
|
from yutipy.kkbox import KKBox
|
|
9
11
|
from yutipy.models import MusicInfo, MusicInfos
|
|
10
12
|
from yutipy.musicyt import MusicYT
|
|
11
13
|
from yutipy.spotify import Spotify
|
|
12
14
|
from yutipy.utils.cheap_utils import is_valid_string
|
|
15
|
+
from yutipy.utils.logger import logger
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class YutipyMusic:
|
|
@@ -22,9 +25,53 @@ class YutipyMusic:
|
|
|
22
25
|
def __init__(self) -> None:
|
|
23
26
|
"""Initializes the YutipyMusic class."""
|
|
24
27
|
self.music_info = MusicInfos()
|
|
25
|
-
self.album_art_priority = ["deezer", "
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
self.album_art_priority = ["deezer", "ytmusic", "itunes"]
|
|
29
|
+
self.services = {
|
|
30
|
+
"deezer": Deezer(),
|
|
31
|
+
"itunes": Itunes(),
|
|
32
|
+
"ytmusic": MusicYT(),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
self.services["kkbox"] = KKBox()
|
|
37
|
+
except KKBoxException as e:
|
|
38
|
+
logger.warning(
|
|
39
|
+
f"{self.__class__.__name__}: Skipping KKBox due to KKBoxException: {e}"
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
idx = self.album_art_priority.index("ytmusic")
|
|
43
|
+
self.album_art_priority.insert(idx, "kkbox")
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
self.services["spotify"] = Spotify()
|
|
47
|
+
except SpotifyException as e:
|
|
48
|
+
logger.warning(
|
|
49
|
+
f"{self.__class__.__name__}: Skipping Spotify due to SpotifyException: {e}"
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
idx = self.album_art_priority.index("ytmusic")
|
|
53
|
+
self.album_art_priority.insert(idx, "spotify")
|
|
54
|
+
self.normalize_non_english = True
|
|
55
|
+
self._translation_session = requests.Session()
|
|
56
|
+
|
|
57
|
+
# Assign the translation session to each service
|
|
58
|
+
for service in self.services.values():
|
|
59
|
+
if hasattr(service, "_translation_session"):
|
|
60
|
+
service._translation_session = self._translation_session
|
|
61
|
+
|
|
62
|
+
def __enter__(self) -> "YutipyMusic":
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
|
66
|
+
self.close_sessions()
|
|
67
|
+
|
|
68
|
+
def search(
|
|
69
|
+
self,
|
|
70
|
+
artist: str,
|
|
71
|
+
song: str,
|
|
72
|
+
limit: int = 5,
|
|
73
|
+
normalize_non_english: bool = True,
|
|
74
|
+
) -> Optional[MusicInfos]:
|
|
28
75
|
"""
|
|
29
76
|
Searches for a song by artist and title.
|
|
30
77
|
|
|
@@ -34,6 +81,10 @@ class YutipyMusic:
|
|
|
34
81
|
The name of the artist.
|
|
35
82
|
song : str
|
|
36
83
|
The title of the song.
|
|
84
|
+
limit: int, optional
|
|
85
|
+
The number of items to retrieve from all APIs. ``limit >=1 and <= 50``. Default is ``5``.
|
|
86
|
+
normalize_non_english : bool, optional
|
|
87
|
+
Whether to normalize non-English characters for comparison. Default is ``True``.
|
|
37
88
|
|
|
38
89
|
Returns
|
|
39
90
|
-------
|
|
@@ -45,18 +96,18 @@ class YutipyMusic:
|
|
|
45
96
|
"Artist and song names must be valid strings and can't be empty."
|
|
46
97
|
)
|
|
47
98
|
|
|
48
|
-
|
|
49
|
-
(Deezer, "deezer"),
|
|
50
|
-
(Itunes, "itunes"),
|
|
51
|
-
(KKBox, "kkbox"),
|
|
52
|
-
(MusicYT, "musicyt"),
|
|
53
|
-
(Spotify, "spotify"),
|
|
54
|
-
]
|
|
99
|
+
self.normalize_non_english = normalize_non_english
|
|
55
100
|
|
|
56
101
|
with ThreadPoolExecutor() as executor:
|
|
57
102
|
futures = {
|
|
58
|
-
executor.submit(
|
|
59
|
-
|
|
103
|
+
executor.submit(
|
|
104
|
+
service.search,
|
|
105
|
+
artist=artist,
|
|
106
|
+
song=song,
|
|
107
|
+
limit=limit,
|
|
108
|
+
normalize_non_english=self.normalize_non_english,
|
|
109
|
+
): name
|
|
110
|
+
for name, service in self.services.items()
|
|
60
111
|
}
|
|
61
112
|
|
|
62
113
|
for future in as_completed(futures):
|
|
@@ -117,9 +168,16 @@ class YutipyMusic:
|
|
|
117
168
|
self.music_info.id[service_name] = result.id
|
|
118
169
|
self.music_info.url[service_name] = result.url
|
|
119
170
|
|
|
171
|
+
def close_sessions(self) -> None:
|
|
172
|
+
"""Closes the sessions for all services."""
|
|
173
|
+
for service in self.services.values():
|
|
174
|
+
if hasattr(service, "close_session"):
|
|
175
|
+
service.close_session()
|
|
176
|
+
|
|
120
177
|
|
|
121
178
|
if __name__ == "__main__":
|
|
122
179
|
yutipy_music = YutipyMusic()
|
|
123
180
|
artist_name = input("Artist Name: ")
|
|
124
181
|
song_name = input("Song Name: ")
|
|
125
182
|
pprint(yutipy_music.search(artist_name, song_name))
|
|
183
|
+
yutipy_music.close_sessions()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
yutipy/__init__.py,sha256=UIymi9iT8s1nGuqcQcFudZYNsJDR4RzXe6LUaN9V8l0,289
|
|
2
|
+
yutipy/deezer.py,sha256=cTQpnOgS81mUw_EJ0b2Z1r9CG9ircHCxl9vL-oAJ9Lg,9622
|
|
3
|
+
yutipy/exceptions.py,sha256=4L0Oe1PwFP34LoFTy-Fruipk7uB-JkaackRmkjlaZJU,1138
|
|
4
|
+
yutipy/itunes.py,sha256=9VOcIfjezj8WAVpsne9bbBPoIUbn0GO0UWrCrXzbrE4,6758
|
|
5
|
+
yutipy/kkbox.py,sha256=V-A7nFa1oNrTBOEu7DFzMHAHFZ5cPz55GInEuYfTSfg,13466
|
|
6
|
+
yutipy/models.py,sha256=si_qgaApAYDfSyE8cl_Yg4IfWOtxk1I5JCT8bZsmV4U,1931
|
|
7
|
+
yutipy/musicyt.py,sha256=igqz99bJYGtUGyZAmEJoQbdGIF-1YFmeaV5HmlhwqkA,8441
|
|
8
|
+
yutipy/spotify.py,sha256=IrtCtay8UWh8zsa7d1JKrDUuZLa-PDwh8zb3K629tLo,15294
|
|
9
|
+
yutipy/yutipy_music.py,sha256=zwnJWKy6Kdh-ewz2jHzRSo4ntKoigJa7MMKMzwgcr7A,6214
|
|
10
|
+
yutipy/utils/__init__.py,sha256=7UFcFZ7fBtNXOTngjnRD3MeobT3x5UT2Gag94TXVgLk,169
|
|
11
|
+
yutipy/utils/cheap_utils.py,sha256=TMas4QwOXxdIZ0t3bxxvLAQdeFCGZyzuxicbpzWJ0a0,4542
|
|
12
|
+
yutipy/utils/logger.py,sha256=2_b2FlDwUVpdPdqiwweR8Xr2tZOq0qGUGcekC5lXq2M,130
|
|
13
|
+
yutipy-1.4.0.dist-info/licenses/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
|
|
14
|
+
yutipy-1.4.0.dist-info/METADATA,sha256=U9Zl_5_k8p7xkzDGlfHGEIJuh1vR_HsmHqDIC8O4qTo,6489
|
|
15
|
+
yutipy-1.4.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
16
|
+
yutipy-1.4.0.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
|
|
17
|
+
yutipy-1.4.0.dist-info/RECORD,,
|
yutipy-1.3.1.dist-info/RECORD
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
yutipy/__init__.py,sha256=UIymi9iT8s1nGuqcQcFudZYNsJDR4RzXe6LUaN9V8l0,289
|
|
2
|
-
yutipy/deezer.py,sha256=eLaDNBnpdFpvdxEQrgTZiLl-FBjSs1R0CKEgpgRi0Us,8666
|
|
3
|
-
yutipy/exceptions.py,sha256=4L0Oe1PwFP34LoFTy-Fruipk7uB-JkaackRmkjlaZJU,1138
|
|
4
|
-
yutipy/itunes.py,sha256=SmW6bCGKB9ADG2FxggGDmEOvqAIvuRi7aOkBSyHm2HQ,5841
|
|
5
|
-
yutipy/kkbox.py,sha256=9sz8eb-pu-47DTP_4kgackX1ZLijnCnz7q_1M8BqLWo,12185
|
|
6
|
-
yutipy/models.py,sha256=si_qgaApAYDfSyE8cl_Yg4IfWOtxk1I5JCT8bZsmV4U,1931
|
|
7
|
-
yutipy/musicyt.py,sha256=kvRYuhlNVuvy-WahJq895T3JAzmGTTiKB89r_uTMTAI,7155
|
|
8
|
-
yutipy/spotify.py,sha256=Oaktvdz7gLe2_PaktOmjlc8QpfhZWkmQnIZDmwoXlRE,13608
|
|
9
|
-
yutipy/yutipy_music.py,sha256=XEdTlcxxWvAwZiaGWX4FSVmo9TnUnxDrYtAeELek6R0,4091
|
|
10
|
-
yutipy/utils/__init__.py,sha256=7UFcFZ7fBtNXOTngjnRD3MeobT3x5UT2Gag94TXVgLk,169
|
|
11
|
-
yutipy/utils/cheap_utils.py,sha256=Yl0ssVbyvrdVeSPF7PqpGJpwTYLVa4CMgLtr6uk67v0,3104
|
|
12
|
-
yutipy-1.3.1.dist-info/licenses/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
|
|
13
|
-
yutipy-1.3.1.dist-info/METADATA,sha256=AQDaOSZ2OWT7pd_tirhnDYgVaMJuixntu6CUNlPH1RU,6489
|
|
14
|
-
yutipy-1.3.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
15
|
-
yutipy-1.3.1.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
|
|
16
|
-
yutipy-1.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|