yutipy 1.3.0__tar.gz → 1.3.2__tar.gz
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-1.3.0 → yutipy-1.3.2}/PKG-INFO +1 -1
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/usage_examples.rst +4 -4
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/deezer.py +5 -2
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/itunes.py +5 -2
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/kkbox.py +5 -2
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/musicyt.py +5 -2
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/spotify.py +6 -3
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/yutipy_music.py +34 -14
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy.egg-info/PKG-INFO +1 -1
- {yutipy-1.3.0 → yutipy-1.3.2}/.gitattributes +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/.github/FUNDING.yml +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/.github/workflows/pytest-unit-testing.yml +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/.github/workflows/release.yml +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/.gitignore +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/.readthedocs.yaml +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/LICENSE +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/MANIFEST.in +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/README.md +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/Makefile +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/_static/yutipy_header.png +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/_static/yutipy_logo.png +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/api_reference.rst +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/available_platforms.rst +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/conf.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/faq.rst +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/index.rst +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/installation.rst +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/make.bat +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/docs/requirements.txt +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/pyproject.toml +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/requirements-dev.txt +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/requirements.txt +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/setup.cfg +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/__init__.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/test_deezer.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/test_itunes.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/test_kkbox.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/test_models.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/test_musicyt.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/test_spotify.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/tests/test_utils.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/__init__.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/exceptions.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/models.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/utils/__init__.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy/utils/cheap_utils.py +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy.egg-info/SOURCES.txt +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy.egg-info/dependency_links.txt +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy.egg-info/requires.txt +0 -0
- {yutipy-1.3.0 → yutipy-1.3.2}/yutipy.egg-info/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ Usage Examples
|
|
|
5
5
|
Here's a quick example of how to use the **yutipy** package to search for a song:
|
|
6
6
|
|
|
7
7
|
.. important::
|
|
8
|
-
All examples here—except for the `YouTube Music`_
|
|
8
|
+
All examples here—except for the `YouTube Music`_—use the ``with`` context manager to initialize an instance of the respective class,
|
|
9
9
|
as those classes internally use ``requests.Session()`` for making requests to APIs.
|
|
10
10
|
This approach ensures that the session is automatically closed once you exit the context. Although using ``with`` is not mandatory,
|
|
11
11
|
if you instantiate an object without it, you are responsible for closing the session after use by calling the ``close_session()`` method on that object.
|
|
@@ -118,6 +118,6 @@ Yutipy Music
|
|
|
118
118
|
|
|
119
119
|
from yutipy.yutify_music import YutipyMusic
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
with YutipyMusic() as yutipy_music:
|
|
122
|
+
result = yutify_music.search("Artist Name", "Song Title")
|
|
123
|
+
print(result)
|
|
@@ -41,7 +41,7 @@ class Deezer:
|
|
|
41
41
|
"""Checks if the session is closed."""
|
|
42
42
|
return self._is_session_closed
|
|
43
43
|
|
|
44
|
-
def search(self, artist: str, song: str) -> Optional[MusicInfo]:
|
|
44
|
+
def search(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
|
|
45
45
|
"""
|
|
46
46
|
Searches for a song by artist and title.
|
|
47
47
|
|
|
@@ -51,6 +51,9 @@ class Deezer:
|
|
|
51
51
|
The name of the artist.
|
|
52
52
|
song : str
|
|
53
53
|
The title of the song.
|
|
54
|
+
limit: int, optional
|
|
55
|
+
The number of items to retrieve from API.
|
|
56
|
+
``limit >=1 and <= 50``. Default is ``10``.
|
|
54
57
|
|
|
55
58
|
Returns
|
|
56
59
|
-------
|
|
@@ -66,7 +69,7 @@ class Deezer:
|
|
|
66
69
|
|
|
67
70
|
for search_type in search_types:
|
|
68
71
|
endpoint = f"{self.api_url}/search/{search_type}"
|
|
69
|
-
query = f'?q=artist:"{artist}" {search_type}:"{song}"&limit=
|
|
72
|
+
query = f'?q=artist:"{artist}" {search_type}:"{song}"&limit={limit}'
|
|
70
73
|
query_url = endpoint + query
|
|
71
74
|
|
|
72
75
|
try:
|
|
@@ -42,7 +42,7 @@ class Itunes:
|
|
|
42
42
|
"""Checks if the session is closed."""
|
|
43
43
|
return self._is_session_closed
|
|
44
44
|
|
|
45
|
-
def search(self, artist: str, song: str) -> Optional[MusicInfo]:
|
|
45
|
+
def search(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
|
|
46
46
|
"""
|
|
47
47
|
Searches for a song by artist and title.
|
|
48
48
|
|
|
@@ -52,6 +52,9 @@ class Itunes:
|
|
|
52
52
|
The name of the artist.
|
|
53
53
|
song : str
|
|
54
54
|
The title of the song.
|
|
55
|
+
limit: int, optional
|
|
56
|
+
The number of items to retrieve from API.
|
|
57
|
+
``limit >=1 and <= 50``. Default is ``10``.
|
|
55
58
|
|
|
56
59
|
Returns
|
|
57
60
|
-------
|
|
@@ -66,7 +69,7 @@ class Itunes:
|
|
|
66
69
|
entities = ["song", "album"]
|
|
67
70
|
for entity in entities:
|
|
68
71
|
endpoint = f"{self.api_url}/search"
|
|
69
|
-
query = f"?term={artist} - {song}&media=music&entity={entity}&limit=
|
|
72
|
+
query = f"?term={artist} - {song}&media=music&entity={entity}&limit={limit}"
|
|
70
73
|
query_url = endpoint + query
|
|
71
74
|
|
|
72
75
|
try:
|
|
@@ -134,7 +134,7 @@ class KKBox:
|
|
|
134
134
|
self.__start_time = time.time()
|
|
135
135
|
|
|
136
136
|
def search(
|
|
137
|
-
self, artist: str, song: str, territory: str = "TW"
|
|
137
|
+
self, artist: str, song: str, territory: str = "TW", limit: int = 10,
|
|
138
138
|
) -> Optional[MusicInfo]:
|
|
139
139
|
"""
|
|
140
140
|
Searches for a song by artist and title.
|
|
@@ -148,6 +148,9 @@ class KKBox:
|
|
|
148
148
|
territory : str
|
|
149
149
|
Two-letter country codes from ISO 3166-1 alpha-2.
|
|
150
150
|
Allowed values: ``HK``, ``JP``, ``MY``, ``SG``, ``TW``.
|
|
151
|
+
limit: int, optional
|
|
152
|
+
The number of items to retrieve from API.
|
|
153
|
+
``limit >=1 and <= 50``. Default is ``10``.
|
|
151
154
|
|
|
152
155
|
Returns
|
|
153
156
|
-------
|
|
@@ -161,7 +164,7 @@ class KKBox:
|
|
|
161
164
|
|
|
162
165
|
self.__refresh_token_if_expired()
|
|
163
166
|
|
|
164
|
-
query = f"?q={artist} - {song}&type=track,album&territory={territory}&limit=
|
|
167
|
+
query = f"?q={artist} - {song}&type=track,album&territory={territory}&limit={limit}"
|
|
165
168
|
query_url = f"{self.api_url}/search{query}"
|
|
166
169
|
|
|
167
170
|
try:
|
|
@@ -20,7 +20,7 @@ class MusicYT:
|
|
|
20
20
|
"""Initializes the YouTube Music class and sets up the session."""
|
|
21
21
|
self.ytmusic = YTMusic()
|
|
22
22
|
|
|
23
|
-
def search(self, artist: str, song: str) -> Optional[MusicInfo]:
|
|
23
|
+
def search(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
|
|
24
24
|
"""
|
|
25
25
|
Searches for a song by artist and title.
|
|
26
26
|
|
|
@@ -30,6 +30,9 @@ class MusicYT:
|
|
|
30
30
|
The name of the artist.
|
|
31
31
|
song : str
|
|
32
32
|
The title of the song.
|
|
33
|
+
limit: int, optional
|
|
34
|
+
The number of items to retrieve from API.
|
|
35
|
+
``limit >=1 and <= 50``. Default is ``10``.
|
|
33
36
|
|
|
34
37
|
Returns
|
|
35
38
|
-------
|
|
@@ -44,7 +47,7 @@ class MusicYT:
|
|
|
44
47
|
query = f"{artist} - {song}"
|
|
45
48
|
|
|
46
49
|
try:
|
|
47
|
-
results = self.ytmusic.search(query=query)
|
|
50
|
+
results = self.ytmusic.search(query=query, limit=limit)
|
|
48
51
|
except exceptions.YTMusicServerError as e:
|
|
49
52
|
raise NetworkException(f"Network error occurred: {e}")
|
|
50
53
|
|
|
@@ -138,7 +138,7 @@ class Spotify:
|
|
|
138
138
|
self.__header, self.__expires_in = self.__authenticate()
|
|
139
139
|
self.__start_time = time.time()
|
|
140
140
|
|
|
141
|
-
def search(self, artist: str, song: str) -> Optional[MusicInfo]:
|
|
141
|
+
def search(self, artist: str, song: str, limit: int = 10) -> Optional[MusicInfo]:
|
|
142
142
|
"""
|
|
143
143
|
Searches for a song by artist and title.
|
|
144
144
|
|
|
@@ -148,6 +148,9 @@ class Spotify:
|
|
|
148
148
|
The name of the artist.
|
|
149
149
|
song : str
|
|
150
150
|
The title of the song.
|
|
151
|
+
limit: int, optional
|
|
152
|
+
The number of items to retrieve from API.
|
|
153
|
+
``limit >=1 and <= 50``. Default is ``10``.
|
|
151
154
|
|
|
152
155
|
Returns
|
|
153
156
|
-------
|
|
@@ -161,8 +164,8 @@ class Spotify:
|
|
|
161
164
|
|
|
162
165
|
music_info = None
|
|
163
166
|
queries = [
|
|
164
|
-
f"?q=artist:{artist} track:{song}&type=track&limit=
|
|
165
|
-
f"?q=artist:{artist} album:{song}&type=album&limit=
|
|
167
|
+
f"?q=artist:{artist} track:{song}&type=track&limit={limit}",
|
|
168
|
+
f"?q=artist:{artist} album:{song}&type=album&limit={limit}",
|
|
166
169
|
]
|
|
167
170
|
|
|
168
171
|
for query in queries:
|
|
@@ -22,9 +22,22 @@ class YutipyMusic:
|
|
|
22
22
|
def __init__(self) -> None:
|
|
23
23
|
"""Initializes the YutipyMusic class."""
|
|
24
24
|
self.music_info = MusicInfos()
|
|
25
|
-
self.album_art_priority = ["deezer", "kkbox", "spotify", "
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
self.album_art_priority = ["deezer", "kkbox", "spotify", "ytmusic", "itunes"]
|
|
26
|
+
self.services = {
|
|
27
|
+
"deezer": Deezer(),
|
|
28
|
+
"itunes": Itunes(),
|
|
29
|
+
"kkbox": KKBox(),
|
|
30
|
+
"ytmusic": MusicYT(),
|
|
31
|
+
"spotify": Spotify(),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def __enter__(self) -> "YutipyMusic":
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
|
38
|
+
self.close_sessions()
|
|
39
|
+
|
|
40
|
+
def search(self, artist: str, song: str, limit: int = 5) -> Optional[MusicInfos]:
|
|
28
41
|
"""
|
|
29
42
|
Searches for a song by artist and title.
|
|
30
43
|
|
|
@@ -34,6 +47,9 @@ class YutipyMusic:
|
|
|
34
47
|
The name of the artist.
|
|
35
48
|
song : str
|
|
36
49
|
The title of the song.
|
|
50
|
+
limit: int, optional
|
|
51
|
+
The number of items to retrieve from all APIs.
|
|
52
|
+
``limit >=1 and <= 50``. Default is ``5``.
|
|
37
53
|
|
|
38
54
|
Returns
|
|
39
55
|
-------
|
|
@@ -45,18 +61,12 @@ class YutipyMusic:
|
|
|
45
61
|
"Artist and song names must be valid strings and can't be empty."
|
|
46
62
|
)
|
|
47
63
|
|
|
48
|
-
services = [
|
|
49
|
-
(Deezer, "deezer"),
|
|
50
|
-
(Itunes, "itunes"),
|
|
51
|
-
(KKBox, "kkbox"),
|
|
52
|
-
(MusicYT, "musicyt"),
|
|
53
|
-
(Spotify, "spotify"),
|
|
54
|
-
]
|
|
55
|
-
|
|
56
64
|
with ThreadPoolExecutor() as executor:
|
|
57
65
|
futures = {
|
|
58
|
-
executor.submit(
|
|
59
|
-
|
|
66
|
+
executor.submit(
|
|
67
|
+
service.search, artist=artist, song=song, limit=limit
|
|
68
|
+
): name
|
|
69
|
+
for name, service in self.services.items()
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
for future in as_completed(futures):
|
|
@@ -64,7 +74,10 @@ class YutipyMusic:
|
|
|
64
74
|
result = future.result()
|
|
65
75
|
self._combine_results(result, service_name)
|
|
66
76
|
|
|
67
|
-
|
|
77
|
+
if len(self.music_info.url) == 0:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
return self.music_info
|
|
68
81
|
|
|
69
82
|
def _combine_results(self, result: Optional[MusicInfo], service_name: str) -> None:
|
|
70
83
|
"""
|
|
@@ -114,9 +127,16 @@ class YutipyMusic:
|
|
|
114
127
|
self.music_info.id[service_name] = result.id
|
|
115
128
|
self.music_info.url[service_name] = result.url
|
|
116
129
|
|
|
130
|
+
def close_sessions(self) -> None:
|
|
131
|
+
"""Closes the sessions for all services."""
|
|
132
|
+
for service in self.services.values():
|
|
133
|
+
if hasattr(service, "close_session"):
|
|
134
|
+
service.close_session()
|
|
135
|
+
|
|
117
136
|
|
|
118
137
|
if __name__ == "__main__":
|
|
119
138
|
yutipy_music = YutipyMusic()
|
|
120
139
|
artist_name = input("Artist Name: ")
|
|
121
140
|
song_name = input("Song Name: ")
|
|
122
141
|
pprint(yutipy_music.search(artist_name, song_name))
|
|
142
|
+
yutipy_music.close_sessions()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|