From 2b5d19abb1820cb719b5abc817370aee90b9c5f5 Mon Sep 17 00:00:00 2001 From: fritz stelluto Date: Tue, 8 Oct 2024 12:17:14 +0200 Subject: [PATCH] fix #656: attempt to find runs in subtitle if not in title (#657) * fix #656: attempt to find runs in subtitle if not in title * refactor * remove fix by gotofritz --------- Co-authored-by: sigma67 --- tests/conftest.py | 6 ------ tests/mixins/test_playlists.py | 37 +++++++++++++++++---------------- ytmusicapi/mixins/playlists.py | 22 ++------------------ ytmusicapi/parsers/playlists.py | 32 +++++++++++++++++----------- 4 files changed, 41 insertions(+), 56 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2a9b66e7..c0f484dc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,12 +34,6 @@ def fixture_sample_video() -> str: return "hpSrLjc5SMs" -@pytest.fixture(name="sample_playlist") -def fixture_sample_playlist() -> str: - """very large playlist""" - return "PL6bPxvf5dW5clc3y9wAoslzqUrmkZ5c-u" - - @pytest.fixture(name="browser_filepath") def fixture_browser_filepath(config) -> str: return get_resource(config["auth"]["browser_file"]) diff --git a/tests/mixins/test_playlists.py b/tests/mixins/test_playlists.py index ec513703..b7ed2dc2 100644 --- a/tests/mixins/test_playlists.py +++ b/tests/mixins/test_playlists.py @@ -42,32 +42,33 @@ def test_get_playlist_2024(self, yt, test_file, owned): if track["videoType"] == "MUSIC_VIDEO_TYPE_ATV": assert isinstance(track["album"]["name"], str) and track["album"]["name"] - def test_get_playlist_foreign(self, yt_empty, yt_oauth, sample_playlist): - with pytest.raises(Exception): - yt_empty.get_playlist("PLABC") - playlist = yt_empty.get_playlist( - "RDCLAK5uy_nfjzC9YC1NVPPZHvdoAtKVBOILMDOuxOs", limit=300, suggestions_limit=7 - ) + @pytest.mark.parametrize( + "playlist_id, tracks_len, related_len", + [ + ("RDCLAK5uy_nfjzC9YC1NVPPZHvdoAtKVBOILMDOuxOs", 200, 10), + ("PLj4BSJLnVpNyIjbCWXWNAmybc97FXLlTk", 200, 0), # no related tracks + ("PL6bPxvf5dW5clc3y9wAoslzqUrmkZ5c-u", 1000, 10), # very large + ("PLZ6Ih9wLHQ2Hm2d3Cb0iV48Z2hQjGRyNz", 300, 10), # runs in subtitle, not title + ], + ) + def test_get_playlist_foreign(self, yt_oauth, playlist_id, tracks_len, related_len): + playlist = yt_oauth.get_playlist(playlist_id, limit=None, related=True) assert len(playlist["duration"]) > 5 - assert playlist["trackCount"] > 200 - assert len(playlist["tracks"]) > 200 + assert playlist["trackCount"] > tracks_len + assert len(playlist["tracks"]) > tracks_len + assert len(playlist["related"]) == related_len assert "suggestions" not in playlist assert playlist["owned"] is False + def test_get_playlist_empty(self, yt_empty): + with pytest.raises(Exception): + yt_empty.get_playlist("PLABC") + + def test_get_playlist_no_track_count(self, yt_oauth): playlist = yt_oauth.get_playlist("RDATgXd-") assert playlist["trackCount"] is None # playlist has no trackCount assert len(playlist["tracks"]) >= 100 - playlist = yt_oauth.get_playlist("PLj4BSJLnVpNyIjbCWXWNAmybc97FXLlTk", limit=None, related=True) - assert playlist["trackCount"] > 200 - assert len(playlist["tracks"]) > 200 - assert len(playlist["related"]) == 0 - - playlist = yt_oauth.get_playlist(sample_playlist, limit=1100) - assert playlist["trackCount"] > 1000 - assert len(playlist["tracks"]) > 1000 - assert len(playlist["related"]) == 0 - @pytest.mark.parametrize("language", SUPPORTED_LANGUAGES) def test_get_playlist_languages(self, language): yt = YTMusic(language=language) diff --git a/ytmusicapi/mixins/playlists.py b/ytmusicapi/mixins/playlists.py index 80ffe780..4cab22ad 100644 --- a/ytmusicapi/mixins/playlists.py +++ b/ytmusicapi/mixins/playlists.py @@ -134,28 +134,10 @@ def get_playlist( if description_shelf else None ) - playlist["thumbnails"] = nav(header, THUMBNAILS) - playlist["title"] = nav(header, TITLE_TEXT) - playlist.update(parse_song_runs(nav(header, SUBTITLE_RUNS)[2 + playlist["owned"] * 2 :])) - playlist["views"] = None - playlist["duration"] = None - if "runs" in header["secondSubtitle"]: - second_subtitle_runs = header["secondSubtitle"]["runs"] - has_views = (len(second_subtitle_runs) > 3) * 2 - playlist["views"] = None if not has_views else to_int(second_subtitle_runs[0]["text"]) - has_duration = (len(second_subtitle_runs) > 1) * 2 - playlist["duration"] = ( - None if not has_duration else second_subtitle_runs[has_views + has_duration]["text"] - ) - song_count_text = second_subtitle_runs[has_views + 0]["text"] - song_count_search = re.findall(r"\d+", song_count_text) - # extract the digits from the text, return 0 if no match - song_count = to_int("".join(song_count_search)) if song_count_search is not None else None - else: - song_count = None + playlist.update(parse_playlist_header_meta(header)) - playlist["trackCount"] = song_count + playlist.update(parse_song_runs(nav(header, SUBTITLE_RUNS)[2 + playlist["owned"] * 2 :])) request_func = lambda additionalParams: self._send_request(endpoint, body, additionalParams) diff --git a/ytmusicapi/parsers/playlists.py b/ytmusicapi/parsers/playlists.py index 0fd395a0..d72a4066 100644 --- a/ytmusicapi/parsers/playlists.py +++ b/ytmusicapi/parsers/playlists.py @@ -19,10 +19,9 @@ def parse_playlist_header(response: dict) -> dict[str, Any]: response, [*TWO_COLUMN_RENDERER, *TAB_CONTENT, *SECTION_LIST_ITEM, *RESPONSIVE_HEADER] ) - playlist["title"] = nav(header, TITLE_TEXT) - playlist["thumbnails"] = nav(header, THUMBNAIL_CROPPED, True) + playlist.update(parse_playlist_header_meta(header)) if playlist["thumbnails"] is None: - playlist["thumbnails"] = nav(header, THUMBNAILS) + playlist["thumbnails"] = nav(header, THUMBNAIL_CROPPED, True) playlist["description"] = nav(header, DESCRIPTION, True) run_count = len(nav(header, SUBTITLE_RUNS)) if run_count > 1: @@ -33,24 +32,33 @@ def parse_playlist_header(response: dict) -> dict[str, Any]: if run_count == 5: playlist["year"] = nav(header, SUBTITLE3) - playlist["views"] = None - playlist["duration"] = None - playlist["trackCount"] = None + return playlist + + +def parse_playlist_header_meta(header: dict[str, Any]) -> dict[str, Any]: + playlist_meta = { + "views": None, + "duration": None, + "trackCount": None, + "title": nav(header, TITLE_TEXT, none_if_absent=True), + "thumbnails": nav(header, THUMBNAILS), + } if "runs" in header["secondSubtitle"]: second_subtitle_runs = header["secondSubtitle"]["runs"] has_views = (len(second_subtitle_runs) > 3) * 2 - playlist["views"] = None if not has_views else to_int(second_subtitle_runs[0]["text"]) + playlist_meta["views"] = None if not has_views else to_int(second_subtitle_runs[0]["text"]) has_duration = (len(second_subtitle_runs) > 1) * 2 - playlist["duration"] = ( + playlist_meta["duration"] = ( None if not has_duration else second_subtitle_runs[has_views + has_duration]["text"] ) song_count_text = second_subtitle_runs[has_views + 0]["text"] - song_count_search = re.search(r"\d+", song_count_text) + song_count_search = re.findall(r"\d+", song_count_text) # extract the digits from the text, return 0 if no match - song_count = to_int(song_count_search.group()) if song_count_search is not None else 0 - playlist["trackCount"] = song_count + playlist_meta["trackCount"] = ( + to_int("".join(song_count_search)) if song_count_search is not None else None + ) - return playlist + return playlist_meta def parse_playlist_items(results, menu_entries: Optional[list[list]] = None, is_album=False):