Skip to content

Commit

Permalink
Minor bug fixes, optimizations, and updates
Browse files Browse the repository at this point in the history
  • Loading branch information
bbye98 committed Jan 21, 2024
1 parent 380fe4f commit 0f0baf4
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 161 deletions.
204 changes: 109 additions & 95 deletions src/minim/audio.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/minim/itunes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
iTunes
======
.. moduleauthor:: Benjamin Ye <GitHub: @bbye98>
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>
This module contains a complete implementation of all iTunes Search API
endpoints.
Expand Down
2 changes: 1 addition & 1 deletion src/minim/qobuz.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Qobuz
=====
.. moduleauthor:: Benjamin Ye <GitHub: @bbye98>
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>
This module contains a minimum implementation of the private Qobuz API.
"""
Expand Down
137 changes: 78 additions & 59 deletions src/minim/spotify.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Spotify
=======
.. moduleauthor:: Benjamin Ye <GitHub: @bbye98>
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>
This module contains a complete implementation of all Spotify Web API
endpoints and a minimal implementation to use the private Spotify Lyrics
Expand Down Expand Up @@ -473,9 +473,15 @@ class WebAPI:
* :code:`"web_player"` for a Spotify Web Player access
token.
browser : `bool`, keyword-only, default: :code:`False`
Determines whether a web browser is automatically opened for the
authorization code (with PKCE) flow. If :code:`False`, users
will have to manually open the authorization URL. Not applicable
when `web_framework="playwright"`.
web_framework : `str`, keyword-only, optional
Determines which web framework to use for the authorization code
flow.
(with PKCE) flow.
.. container::
Expand Down Expand Up @@ -672,11 +678,12 @@ def get_scopes(self, categories: Union[str, list[str]]) -> str:

def __init__(
self, *, client_id: str = None, client_secret: str = None,
flow: str = "web_player", web_framework: str = None,
port: Union[int, str] = 8888, redirect_uri: str = None,
scopes: Union[str, list[str]] = "", sp_dc: str = None,
access_token: str = None, refresh_token: str = None,
expiry: Union[datetime.datetime, str] = None,
flow: str = "web_player", browser: bool = False,
web_framework: str = None, port: Union[int, str] = 8888,
redirect_uri: str = None, scopes: Union[str, list[str]] = "",
sp_dc: str = None, access_token: str = None,
refresh_token: str = None,
expiry: Union[datetime.datetime, str] = None,
overwrite: bool = False, save: bool = True) -> None:

"""
Expand All @@ -702,8 +709,8 @@ def __init__(

self.set_flow(
flow, client_id=client_id, client_secret=client_secret,
web_framework=web_framework, port=port, redirect_uri=redirect_uri,
scopes=scopes, sp_dc=sp_dc, save=save
browser=browser, web_framework=web_framework, port=port,
redirect_uri=redirect_uri, scopes=scopes, sp_dc=sp_dc, save=save
)
self.set_access_token(access_token, refresh_token=refresh_token,
expiry=expiry)
Expand Down Expand Up @@ -758,36 +765,7 @@ def _get_authorization_code(self, code_challenge: str = None) -> str:
params["code_challenge_method"] = "S256"
auth_url = f"{self.AUTH_URL}?{urllib.parse.urlencode(params)}"

if self._web_framework == "http.server":
httpd = HTTPServer(("", self._port), _SpotifyRedirectHandler)
webbrowser.open(auth_url)
httpd.handle_request()
queries = httpd.response

elif self._web_framework == "flask":
app = Flask(__name__)
json_file = DIR_TEMP / "minim_spotify.json"

@app.route("/callback", methods=["GET"])
def _callback() -> str:
if "error" in request.args:
return "Access denied. You may close this page now."
with open(json_file, "w") as f:
json.dump(request.args, f)
return "Access granted. You may close this page now."

server = Process(target=app.run, args=("0.0.0.0", self._port))
server.start()
webbrowser.open(auth_url)
while not json_file.is_file():
time.sleep(0.1)
server.terminate()

with open(json_file, "rb") as f:
queries = json.load(f)
json_file.unlink()

elif self._web_framework == "playwright":
if self._web_framework == "playwright":
har_file = DIR_TEMP / "minim_spotify.har"

with sync_playwright() as playwright:
Expand All @@ -812,15 +790,47 @@ def _callback() -> str:
har_file.unlink()

else:
print("To grant Minim access to Spotify data and features, "
"open the following link in your web browser:\n\n"
f"{auth_url}\n")
uri = input("After authorizing Minim to access Spotify on "
"your behalf, copy and paste the URI beginning "
f"with '{self._redirect_uri}' below.\n\nURI: ")
queries = dict(
urllib.parse.parse_qsl(urllib.parse.urlparse(uri).query)
)
if self._browser:
webbrowser.open(auth_url)
else:
print("To grant Minim access to Spotify data and "
"features, open the following link in your web "
f"browser:\n\n{auth_url}\n")

if self._web_framework == "http.server":
httpd = HTTPServer(("", self._port), _SpotifyRedirectHandler)
httpd.handle_request()
queries = httpd.response

elif self._web_framework == "flask":
app = Flask(__name__)
json_file = DIR_TEMP / "minim_spotify.json"

@app.route("/callback", methods=["GET"])
def _callback() -> str:
if "error" in request.args:
return "Access denied. You may close this page now."
with open(json_file, "w") as f:
json.dump(request.args, f)
return "Access granted. You may close this page now."

server = Process(target=app.run, args=("0.0.0.0", self._port))
server.start()
while not json_file.is_file():
time.sleep(0.1)
server.terminate()

with open(json_file, "rb") as f:
queries = json.load(f)
json_file.unlink()

else:
uri = input("After authorizing Minim to access Spotify on "
"your behalf, copy and paste the URI beginning "
f"with '{self._redirect_uri}' below.\n\nURI: ")
queries = dict(
urllib.parse.parse_qsl(urllib.parse.urlparse(uri).query)
)

if "error" in queries:
raise RuntimeError(f"Authorization failed. Error: {queries['error']}")
Expand Down Expand Up @@ -1045,10 +1055,10 @@ def set_access_token(

def set_flow(
self, flow: str, *, client_id: str = None,
client_secret: str = None, web_framework: str = None,
port: Union[int, str] = 8888, redirect_uri: str = None,
scopes: Union[str, list[str]] = "", sp_dc: str = None,
save: bool = True) -> None:
client_secret: str = None, browser: bool = False,
web_framework: str = None, port: Union[int, str] = 8888,
redirect_uri: str = None, scopes: Union[str, list[str]] = "",
sp_dc: str = None, save: bool = True) -> None:

"""
Set the authorization flow.
Expand Down Expand Up @@ -1078,9 +1088,15 @@ def set_flow(
Client secret. Required for all OAuth 2.0 authorization
flows.
browser : `bool`, keyword-only, default: :code:`False`
Determines whether a web browser is automatically opened for
the authorization code (with PKCE) flow. If :code:`False`,
users will have to manually open the authorization URL.
Not applicable when `web_framework="playwright"`.
web_framework : `str`, keyword-only, optional
Web framework used to automatically complete the
authorization code flow.
authorization code (with PKCE) flow.
.. container::
Expand Down Expand Up @@ -1124,9 +1140,10 @@ def set_flow(
self._scopes = self.get_scopes("all") if self._sp_dc else ""
else:
self._client_id = client_id or os.environ.get("SPOTIFY_CLIENT_ID")
self._client_secret = (client_secret
or os.environ.get("SPOTIFY_CLIENT_SECRET"))
self._client_secret = \
client_secret or os.environ.get("SPOTIFY_CLIENT_SECRET")
if flow in {"authorization_code", "pkce"}:
self._browser = browser
self._scopes = " ".join(scopes) if isinstance(scopes, list) \
else scopes

Expand All @@ -1141,14 +1158,16 @@ def set_flow(
"retrieval is not available.")
logging.warning(wmsg)
web_framework = None
else:
elif port:
self._port = port
self._redirect_uri = f"http://localhost:{port}/callback"
else:
self._port = self._redirect_uri = None

self._web_framework = (
web_framework if web_framework is None
or web_framework == "http.server"
or globals()[f"FOUND_{web_framework.upper()}"]
web_framework
if web_framework in {None, "http.server"}
or globals()[f"FOUND_{web_framework.upper()}"]
else None
)
if self._web_framework is None and web_framework:
Expand Down
7 changes: 3 additions & 4 deletions src/minim/tidal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
TIDAL
=====
.. moduleauthor:: Benjamin Ye <GitHub: @bbye98>
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>
This module contains a complete implementation of all public TIDAL API
endpoints and a minimum implementation of the more robust but private
Expand Down Expand Up @@ -6665,10 +6665,9 @@ def get_track_stream(
segment.getAttribute("initialization")
) as r:
stream.extend(r.content)
for i in range(1, sum(int(tl.getAttribute("r"))
if tl.hasAttribute("r") else 1
for i in range(1, sum(int(tl.getAttribute("r") or 1)
for tl in
segment.getElementsByTagName("S")) + 1):
segment.getElementsByTagName("S")) + 2):
with self.session.get(
segment.getAttribute("media").replace(
"$Number$", str(i)
Expand Down
2 changes: 1 addition & 1 deletion src/minim/utility.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Utility functions
=================
.. moduleauthor:: Benjamin Ye <GitHub: @bbye98>
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>
This module contains a collection of utility functions.
"""
Expand Down
Binary file modified tests/data/samples/middle_c.flac
Binary file not shown.
Binary file modified tests/data/samples/middle_c.mp3
Binary file not shown.
Binary file modified tests/data/samples/middle_c_16bit.wav
Binary file not shown.
Binary file modified tests/data/samples/middle_c_aac.m4a
Binary file not shown.
Binary file modified tests/data/samples/middle_c_alac.m4a
Binary file not shown.
Binary file modified tests/data/samples/middle_c_flac.ogg
Binary file not shown.
Binary file modified tests/data/samples/middle_c_opus.ogg
Binary file not shown.
Binary file modified tests/data/samples/middle_c_vorbis.ogg
Binary file not shown.

0 comments on commit 0f0baf4

Please sign in to comment.