yutipy 1.3.2__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 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(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
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
 
@@ -52,8 +61,10 @@ class Deezer:
52
61
  song : str
53
62
  The title of the song.
54
63
  limit: int, optional
55
- The number of items to retrieve from API.
56
- ``limit >=1 and <= 50``. Default is ``10``.
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
+
57
68
 
58
69
  Returns
59
70
  -------
@@ -65,6 +76,8 @@ class Deezer:
65
76
  "Artist and song names must be valid strings and can't be empty."
66
77
  )
67
78
 
79
+ self.normalize_non_english = normalize_non_english
80
+
68
81
  search_types = ["track", "album"]
69
82
 
70
83
  for search_type in search_types:
@@ -208,8 +221,18 @@ class Deezer:
208
221
  """
209
222
  for result in results:
210
223
  if not (
211
- are_strings_similar(result["title"], song)
212
- and are_strings_similar(result["artist"]["name"], artist)
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
+ )
213
236
  ):
214
237
  continue
215
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(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
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
 
@@ -53,8 +62,9 @@ class Itunes:
53
62
  song : str
54
63
  The title of the song.
55
64
  limit: int, optional
56
- The number of items to retrieve from API.
57
- ``limit >=1 and <= 50``. Default is ``10``.
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``.
58
68
 
59
69
  Returns
60
70
  -------
@@ -66,6 +76,8 @@ class Itunes:
66
76
  "Artist and song names must be valid strings and can't be empty."
67
77
  )
68
78
 
79
+ self.normalize_non_english = normalize_non_english
80
+
69
81
  entities = ["song", "album"]
70
82
  for entity in entities:
71
83
  endpoint = f"{self.api_url}/search"
@@ -114,9 +126,17 @@ class Itunes:
114
126
  for result in results:
115
127
  if not (
116
128
  are_strings_similar(
117
- result.get("trackName", result["collectionName"]), song
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,
118
139
  )
119
- and are_strings_similar(result["artistName"], artist)
120
140
  ):
121
141
  continue
122
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, artist: str, song: str, territory: str = "TW", limit: int = 10,
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.
@@ -149,8 +157,9 @@ class KKBox:
149
157
  Two-letter country codes from ISO 3166-1 alpha-2.
150
158
  Allowed values: ``HK``, ``JP``, ``MY``, ``SG``, ``TW``.
151
159
  limit: int, optional
152
- The number of items to retrieve from API.
153
- ``limit >=1 and <= 50``. Default is ``10``.
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``.
154
163
 
155
164
  Returns
156
165
  -------
@@ -162,9 +171,13 @@ class KKBox:
162
171
  "Artist and song names must be valid strings and can't be empty."
163
172
  )
164
173
 
174
+ self.normalize_non_english = normalize_non_english
175
+
165
176
  self.__refresh_token_if_expired()
166
177
 
167
- query = f"?q={artist} - {song}&type=track,album&territory={territory}&limit={limit}"
178
+ query = (
179
+ f"?q={artist} - {song}&type=track,album&territory={territory}&limit={limit}"
180
+ )
168
181
  query_url = f"{self.api_url}/search{query}"
169
182
 
170
183
  try:
@@ -288,12 +301,24 @@ class KKBox:
288
301
  Optional[MusicInfo]
289
302
  The music information if found, otherwise None.
290
303
  """
291
- if not are_strings_similar(track["name"], song):
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
+ ):
292
310
  return None
293
311
 
294
312
  artists_name = track["album"]["artist"]["name"]
295
313
  matching_artists = (
296
- artists_name if are_strings_similar(artists_name, artist) else None
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
297
322
  )
298
323
 
299
324
  if matching_artists:
@@ -336,12 +361,24 @@ class KKBox:
336
361
  Optional[MusicInfo]
337
362
  The music information if found, otherwise None.
338
363
  """
339
- if not are_strings_similar(album["name"], song):
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
+ ):
340
370
  return None
341
371
 
342
372
  artists_name = album["artist"]["name"]
343
373
  matching_artists = (
344
- artists_name if are_strings_similar(artists_name, artist) else None
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
345
382
  )
346
383
 
347
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
- def search(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
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
 
@@ -31,8 +52,9 @@ class MusicYT:
31
52
  song : str
32
53
  The title of the song.
33
54
  limit: int, optional
34
- The number of items to retrieve from API.
35
- ``limit >=1 and <= 50``. Default is ``10``.
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``.
36
58
 
37
59
  Returns
38
60
  -------
@@ -44,6 +66,8 @@ class MusicYT:
44
66
  "Artist and song names must be valid strings and can't be empty."
45
67
  )
46
68
 
69
+ self.normalize_non_english = normalize_non_english
70
+
47
71
  query = f"{artist} - {song}"
48
72
 
49
73
  try:
@@ -79,8 +103,18 @@ class MusicYT:
79
103
  return False
80
104
 
81
105
  return any(
82
- are_strings_similar(result.get("title"), song)
83
- and are_strings_similar(_artist.get("name"), artist)
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
+ )
84
118
  for _artist in result.get("artists", [])
85
119
  )
86
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(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
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
 
@@ -149,8 +158,9 @@ class Spotify:
149
158
  song : str
150
159
  The title of the song.
151
160
  limit: int, optional
152
- The number of items to retrieve from API.
153
- ``limit >=1 and <= 50``. Default is ``10``.
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``.
154
164
 
155
165
  Returns
156
166
  -------
@@ -162,6 +172,8 @@ class Spotify:
162
172
  "Artist and song names must be valid strings and can't be empty."
163
173
  )
164
174
 
175
+ self.normalize_non_english = normalize_non_english
176
+
165
177
  music_info = None
166
178
  queries = [
167
179
  f"?q=artist:{artist} track:{song}&type=track&limit={limit}",
@@ -195,7 +207,13 @@ class Spotify:
195
207
  return music_info
196
208
 
197
209
  def search_advanced(
198
- self, artist: str, song: str, isrc: str = None, upc: str = None
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,
199
217
  ) -> Optional[MusicInfo]:
200
218
  """
201
219
  Searches for a song by artist, title, ISRC, or UPC.
@@ -210,6 +228,10 @@ class Spotify:
210
228
  The ISRC of the track.
211
229
  upc : str, optional
212
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``.
213
235
 
214
236
  Returns
215
237
  -------
@@ -221,12 +243,14 @@ class Spotify:
221
243
  "Artist and song names must be valid strings and can't be empty."
222
244
  )
223
245
 
246
+ self.normalize_non_english = normalize_non_english
247
+
224
248
  self.__refresh_token_if_expired()
225
249
 
226
250
  if isrc:
227
- query = f"?q={artist} {song} isrc:{isrc}&type=track&limit=1"
251
+ query = f"?q={artist} {song} isrc:{isrc}&type=track&limit={limit}"
228
252
  elif upc:
229
- query = f"?q={artist} {song} upc:{upc}&type=album&limit=1"
253
+ query = f"?q={artist} {song} upc:{upc}&type=album&limit={limit}"
230
254
  else:
231
255
  raise InvalidValueException("ISRC or UPC must be provided.")
232
256
 
@@ -340,14 +364,25 @@ class Spotify:
340
364
  Optional[MusicInfo]
341
365
  The music information if found, otherwise None.
342
366
  """
343
- if not are_strings_similar(track["name"], song):
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
+ ):
344
373
  return None
345
374
 
346
375
  artists_name = [x["name"] for x in track["artists"]]
347
376
  matching_artists = [
348
377
  x["name"]
349
378
  for x in track["artists"]
350
- if are_strings_similar(x["name"], artist) or x["id"] in artist_ids
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
351
386
  ]
352
387
 
353
388
  if matching_artists:
@@ -392,14 +427,25 @@ class Spotify:
392
427
  Optional[MusicInfo]
393
428
  The music information if found, otherwise None.
394
429
  """
395
- if not are_strings_similar(album["name"], song):
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
+ ):
396
436
  return None
397
437
 
398
438
  artists_name = [x["name"] for x in album["artists"]]
399
439
  matching_artists = [
400
440
  x["name"]
401
441
  for x in album["artists"]
402
- if are_strings_similar(x["name"], artist) or x["id"] in artist_ids
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
403
449
  ]
404
450
 
405
451
  if matching_artists:
@@ -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 = requests.get(url)
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(str1: str, str2: str, threshold: int = 80) -> bool:
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
- str1 = translate_text(str1)["destination-text"]
56
- str2 = translate_text(str2)["destination-text"]
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
@@ -0,0 +1,4 @@
1
+ import logging
2
+
3
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
4
+ logger = logging.getLogger(__name__)
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,22 +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", "kkbox", "spotify", "ytmusic", "itunes"]
28
+ self.album_art_priority = ["deezer", "ytmusic", "itunes"]
26
29
  self.services = {
27
30
  "deezer": Deezer(),
28
31
  "itunes": Itunes(),
29
- "kkbox": KKBox(),
30
32
  "ytmusic": MusicYT(),
31
- "spotify": Spotify(),
32
33
  }
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
+
34
62
  def __enter__(self) -> "YutipyMusic":
35
63
  return self
36
64
 
37
65
  def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
38
66
  self.close_sessions()
39
67
 
40
- def search(self, artist: str, song: str, limit: int = 5) -> Optional[MusicInfos]:
68
+ def search(
69
+ self,
70
+ artist: str,
71
+ song: str,
72
+ limit: int = 5,
73
+ normalize_non_english: bool = True,
74
+ ) -> Optional[MusicInfos]:
41
75
  """
42
76
  Searches for a song by artist and title.
43
77
 
@@ -48,8 +82,9 @@ class YutipyMusic:
48
82
  song : str
49
83
  The title of the song.
50
84
  limit: int, optional
51
- The number of items to retrieve from all APIs.
52
- ``limit >=1 and <= 50``. Default is ``5``.
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``.
53
88
 
54
89
  Returns
55
90
  -------
@@ -61,10 +96,16 @@ class YutipyMusic:
61
96
  "Artist and song names must be valid strings and can't be empty."
62
97
  )
63
98
 
99
+ self.normalize_non_english = normalize_non_english
100
+
64
101
  with ThreadPoolExecutor() as executor:
65
102
  futures = {
66
103
  executor.submit(
67
- service.search, artist=artist, song=song, limit=limit
104
+ service.search,
105
+ artist=artist,
106
+ song=song,
107
+ limit=limit,
108
+ normalize_non_english=self.normalize_non_english,
68
109
  ): name
69
110
  for name, service in self.services.items()
70
111
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yutipy
3
- Version: 1.3.2
3
+ Version: 1.4.0
4
4
  Summary: A simple package for retrieving music information from various music platforms APIs.
5
5
  Author: Cheap Nightbot
6
6
  Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
@@ -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,,
@@ -1,16 +0,0 @@
1
- yutipy/__init__.py,sha256=UIymi9iT8s1nGuqcQcFudZYNsJDR4RzXe6LUaN9V8l0,289
2
- yutipy/deezer.py,sha256=JNG9iy5xtjXwEjH-mlA0b75-MN0tswh0E3ytFskA5jI,8827
3
- yutipy/exceptions.py,sha256=4L0Oe1PwFP34LoFTy-Fruipk7uB-JkaackRmkjlaZJU,1138
4
- yutipy/itunes.py,sha256=cyddyZJscz7nD0mnJEimyBenZKlyjTOaMtcWKcW347w,5994
5
- yutipy/kkbox.py,sha256=8rW9jzfM_RIaf0I0cOUCP_w_We25jFntQeoDl0vvbeg,12347
6
- yutipy/models.py,sha256=si_qgaApAYDfSyE8cl_Yg4IfWOtxk1I5JCT8bZsmV4U,1931
7
- yutipy/musicyt.py,sha256=drlAKIp5yNfEIlhp7eRJj42cJX2YxLvNashliDkdSNc,7324
8
- yutipy/spotify.py,sha256=0B0SLWY1HRkC05zPMRTkSJ9bMogSRBsN9Wq7G0QW7lY,13774
9
- yutipy/yutipy_music.py,sha256=jRMMbjxrlBcTI7AN0TZytLKt01ezUwzApP_byiEo_Dw,4752
10
- yutipy/utils/__init__.py,sha256=7UFcFZ7fBtNXOTngjnRD3MeobT3x5UT2Gag94TXVgLk,169
11
- yutipy/utils/cheap_utils.py,sha256=Yl0ssVbyvrdVeSPF7PqpGJpwTYLVa4CMgLtr6uk67v0,3104
12
- yutipy-1.3.2.dist-info/licenses/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
13
- yutipy-1.3.2.dist-info/METADATA,sha256=Hzj9UM3ExumUN-k2r2VGVymMhbdCTHXMub5ITeXXTUA,6489
14
- yutipy-1.3.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
15
- yutipy-1.3.2.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
16
- yutipy-1.3.2.dist-info/RECORD,,
File without changes