-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Lyrics w. Timestamps #662
base: main
Are you sure you want to change the base?
Conversation
Added `get_lyrics_with_timestamps` to get lyrics with timestamps. The Method doesn't try to parse the response as normal lyrics, if no lyrics with timestamps are returned. (could be changed to do so tho, the format is the same)
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #662 +/- ##
==========================================
- Coverage 94.99% 94.99% -0.01%
==========================================
Files 38 40 +2
Lines 2278 2337 +59
==========================================
+ Hits 2164 2220 +56
- Misses 114 117 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @heinrich26 , pretty cool that you were able to make this work!
I take it you're basically emulating the mobile client to fetch the timed data? Since you're modifying the client info.
Could we replace the previous get_lyrics
with this? I wouldn't want to have two methods for the same purpose in the API if one provides a superset of information.
Finally, to get this merged:
- please ensure the linters pass
- please add a test for the function
Yea, calling the API "as a different frontend" gives you different results. I temporarily changed the client because I thought unforeseen side-effects aren't desirable.As per the implementation of a multipurpose `get_lyrics()`, I would add an argument like `timestamps=True/False`, to keep it backwards compatible and allow the user to always get the same (old) format – with my approach, you'll end up with 2 possible formats.Furthermore, would you keep the original format with the nested `CueRange` or would you rather have a flat dict? I think that nesting is kinda useless.
|
…ads, because vscode didn't show them otherwise
Pushed to the wrong branch :(
because idk where it came from...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, looks much better! :)
As a general rule of thumb: Don't change more than necessary in a single PR. It makes it harder for the maintainer to review the changes (more time consuming), and harder to trace back potential future issues to specific PRs. It will also prolong the time it takes for the PR to get merged.
Note for this review: Please don't resolve threads, I will do so myself after verifying the changes
ytmusicapi/mixins/search.py
Outdated
@@ -204,7 +206,7 @@ def search( | |||
if filter and "playlists" in filter: | |||
filter = "playlists" | |||
elif scope == scopes[1]: | |||
filter = scopes[1] | |||
filter = scopes[1] # type:ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please undo all changes to this file
@@ -181,7 +181,7 @@ def __init__( | |||
try: | |||
cookie = self.base_headers.get("cookie") | |||
self.sapisid = sapisid_from_cookie(cookie) | |||
self.origin = self.base_headers.get("origin", self.base_headers.get("x-origin")) | |||
self.origin = cast(str, self.base_headers.get("origin", self.base_headers.get("x-origin"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please undo changes to this file
ytmusicapi/__init__.py
Outdated
@@ -2,14 +2,16 @@ | |||
|
|||
from ytmusicapi.setup import setup, setup_oauth | |||
from ytmusicapi.ytmusic import YTMusic | |||
from .mixins.browsing import Lyrics, TimedLyrics, LyricLine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please undo the changes to this file. They are not needed and I don't want to start exporting everything from root
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking similar, but then you'll need to create a submodule for these classes to be accessible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can already access them: from ytmusicapi.mixins.browsing import TimedLyrics
It's similar for https://github.com/sigma67/ytmusicapi/blob/main/ytmusicapi/parsers/podcasts.py
Although with #621 in mind, it might be wise to move them to a ytmusicapi.models
module (one for podcasts
and one for lyrics
, I guess)
ytmusicapi/mixins/_utils.py
Outdated
|
||
from ytmusicapi.exceptions import YTMusicUserError | ||
|
||
|
||
OrderType = Literal['a_to_z', 'z_to_a', 'recently_added'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good change in general, but the name is too broad. Since you introduced an ArtistOrderType
elsewhere, this should have a more specific name as well. Maybe LibraryOrderType
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
ytmusicapi/mixins/browsing.py
Outdated
return lyrics | ||
if timestamps: | ||
# restore the old context | ||
self.context["context"]["client"] = copied_context_client # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type ignore should not be needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is technically not a type ignore, but it will supress the typechecker error:
copied_context_client might not be initialized
ytmusicapi/mixins/browsing.py
Outdated
|
||
if timestamps: | ||
# change the client to get lyrics with timestamps (mobile only) | ||
copied_context_client = self.context["context"]["client"].copy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be used in more places in the future, so I would suggest moving it to a separate utility function that returns a deepcopy of the context changed to the mobile context (maybe get_mobile_context
next to initialize_context in helpers.py)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, however I wouldn't deepcopy the context, because someone might want to do weird stuff with references inside self.context. Using shallow-copy here, should structually restore everything to how it was before the call. (so I think)
a cool thing would be using a context manager for use with
, like
with yt.as_mobile():
...
# back to desktop
(contextlib) makes a function a context-manager
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting idea, I like it. But it won't be pretty to wrap the entire function body in a with statement?
And wouldn't it cause problems during parallel execution? If I call a two endpoint with the same YTMusic
instance at the same time, wouldn't one of them break if you perform a shallow copy?
ytmusicapi/mixins/browsing.py
Outdated
@overload | ||
def get_lyrics(self, browseId: str, timestamps: Literal[True] = True) -> Optional[Lyrics|TimedLyrics]: | ||
""" | ||
Returns lyrics of a song or video. When `timestamps` is set, lyrics are returned with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring is now duplicated in three places, please fix
Only the actual function should have the docstring, overloads don't need it
ytmusicapi/mixins/browsing.py
Outdated
@@ -876,7 +1068,7 @@ def get_basejs_url(self): | |||
if match is None: | |||
raise YTMusicError("Could not identify the URL for base.js player.") | |||
|
|||
return YTM_DOMAIN + match.group(1) | |||
return cast(str, YTM_DOMAIN + match.group(1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please don't change unrelated functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cast doesn't do anything but assert a type to the type checker.
However, this should be removed and instead a return type should be added. (fixes the same issue)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, could you either revert the change or add the return type then? I'd prefer the latter
ytmusicapi/mixins/_protocol.py
Outdated
|
||
def _send_request(self, endpoint: str, body: dict, additionalParams: str = "") -> dict: | ||
"""for sending post requests to YouTube Music""" | ||
... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dots are not needed, please remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pylance will report an error if not.
(if you would remove the docstring, it would throw an error anyway, as the function is technically empty. See: https://docs.python.org/3/library/typing.html#typing.overload)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This project uses mypy, so please don't worry about pylance findings. For mypy it's ok to have a docstring in place of ...
It's a matter of personal preference - for me the docstring has a purpose and fulfills the requirement of no empty function body, so I like it better than the nondescript ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note: I have triggered the lint jobs so you can see the mypy and ruff findings remaining.
I recommend taking a look at https://github.com/sigma67/ytmusicapi/blob/main/CONTRIBUTING.rst
where pre-commit is described - if that passes, the lint jobs should pass as well.
reverted some changes that I moved to an extra PR
…y `yt.as_mobile()`
finally used pre-commit to fix those formatting complaints 🤷♂️ |
Added
get_lyrics_with_timestamps
to get lyrics with timestamps. The Method doesn't try to parse the response as normal lyrics, if no lyrics with timestamps are returned. (could be changed to do so tho, the format is the same)