From 123eae7370e712e9d8eee8044759468ba67dcbcc Mon Sep 17 00:00:00 2001 From: John Birdwell <76758414+jcbirdwell@users.noreply.github.com> Date: Sun, 21 Jan 2024 12:39:52 -0600 Subject: [PATCH] =?UTF-8?q?get=5Falbum:=20parse=20more=20data=20from=20res?= =?UTF-8?q?ponse=20+=20correct=20keys=20on=20other=5Fvers=E2=80=A6=20(#523?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * get_album: parse more data from response + correct keys on other_versions * keys: fair point * navigation, browsing: removed duplicate SUBTITLE --------- Co-authored-by: sigma67 --- tests/mixins/test_browsing.py | 56 +++++++++++++++++++++++----------- ytmusicapi/navigation.py | 7 +++-- ytmusicapi/parsers/albums.py | 2 ++ ytmusicapi/parsers/browsing.py | 10 ++++++ 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/tests/mixins/test_browsing.py b/tests/mixins/test_browsing.py index 76def55..378e381 100644 --- a/tests/mixins/test_browsing.py +++ b/tests/mixins/test_browsing.py @@ -67,24 +67,44 @@ def test_get_album_browse_id_issue_470(self, yt): assert escaped_browse_id == "MPREb_scJdtUCpPE2" def test_get_album(self, yt, yt_auth, sample_album): - results = yt_auth.get_album(sample_album) - assert len(results) >= 9 - assert results["tracks"][0]["isExplicit"] - assert all(item["views"] is not None for item in results["tracks"]) - assert all(item["album"] is not None for item in results["tracks"]) - assert results["tracks"][0]["trackNumber"] == 1 - assert "feedbackTokens" in results["tracks"][0] - assert len(results["other_versions"]) >= 1 # appears to be regional - results = yt.get_album("MPREb_BQZvl3BFGay") - assert len(results["tracks"]) == 7 - assert len(results["tracks"][0]["artists"]) == 1 - results = yt.get_album("MPREb_rqH94Zr3NN0") - assert len(results["tracks"][0]["artists"]) == 2 - results = yt.get_album("MPREb_TPH4WqN5pUo") # album with tracks completely removed/missing - assert results["tracks"][0]["trackNumber"] == 3 - assert results["tracks"][13]["trackNumber"] == 18 - results = yt.get_album("MPREb_YuigcYm2erf") # album with track (#8) disabled/greyed out - assert results["tracks"][7]["trackNumber"] is None + album = yt_auth.get_album(sample_album) + assert len(album) >= 9 + assert "isExplicit" in album + assert album["tracks"][0]["isExplicit"] + assert all(item["views"] is not None for item in album["tracks"]) + assert all(item["album"] is not None for item in album["tracks"]) + assert album["tracks"][0]["trackNumber"] == 1 + assert "feedbackTokens" in album["tracks"][0] + album = yt.get_album("MPREb_BQZvl3BFGay") + assert len(album["tracks"]) == 7 + assert len(album["tracks"][0]["artists"]) == 1 + album = yt.get_album("MPREb_rqH94Zr3NN0") + assert len(album["tracks"][0]["artists"]) == 2 + album = yt.get_album("MPREb_TPH4WqN5pUo") # album with tracks completely removed/missing + assert album["tracks"][0]["trackNumber"] == 3 + assert album["tracks"][13]["trackNumber"] == 18 + album = yt.get_album("MPREb_YuigcYm2erf") # album with track (#8) disabled/greyed out + assert album["tracks"][7]["trackNumber"] is None + + def test_get_album_other_versions(self, yt): + # Eminem - Curtain Call: The Hits (Explicit Variant) + album = yt.get_album("MPREb_LQCAymzbaKJ") + assert len(variants := album["other_versions"]) >= 1 # appears to be regional + assert (variant := variants[0])["type"] == "Album" + assert len(variant["artists"]) == 1 + assert variant["artists"][0] == {"name": "Eminem", "id": "UCedvOgsKFzcK3hA5taf3KoQ"} + assert variant["audioPlaylistId"] is not None + + # album that's multi-artist, a single, and has clean version + # Cassö & RAYE - Prada + album = yt.get_album("MPREb_of3qfisa0yU") + assert not album["isExplicit"] + assert (variant := album["other_versions"][0])["type"] == "Single" + assert variant["isExplicit"] + assert len(variant["artists"]) == 3 + assert variant["artists"][0]["id"] == "UCGWMNnI1Ky5bMcRlr73Cj2Q" + assert variant["artists"][1]["name"] == "RAYE" + assert variant["artists"][2] == {"id": "UCb7jnkQW94hzOoWkG14zs4w", "name": "D-Block Europe"} def test_get_song(self, config, yt, yt_oauth, sample_video): song = yt_oauth.get_song(config["uploads"]["private_upload_id"]) # private upload diff --git a/ytmusicapi/navigation.py b/ytmusicapi/navigation.py index a6fe568..e3e157b 100644 --- a/ytmusicapi/navigation.py +++ b/ytmusicapi/navigation.py @@ -21,7 +21,8 @@ MENU_LIKE_STATUS = [*MENU, "topLevelButtons", 0, "likeButtonRenderer", "likeStatus"] MENU_SERVICE = ["menuServiceItemRenderer", "serviceEndpoint"] TOGGLE_MENU = "toggleMenuServiceItemRenderer" -PLAY_BUTTON = ["overlay", "musicItemThumbnailOverlayRenderer", "content", "musicPlayButtonRenderer"] +OVERLAY_RENDERER = ["musicItemThumbnailOverlayRenderer", "content", "musicPlayButtonRenderer"] +PLAY_BUTTON = ["overlay", *OVERLAY_RENDERER] NAVIGATION_BROWSE = ["navigationEndpoint", "browseEndpoint"] NAVIGATION_BROWSE_ID = [*NAVIGATION_BROWSE, "browseId"] PAGE_TYPE = ["browseEndpointContextSupportedConfigs", "browseEndpointContextMusicConfig", "pageType"] @@ -29,7 +30,8 @@ NAVIGATION_VIDEO_ID = ["navigationEndpoint", *WATCH_VIDEO_ID] QUEUE_VIDEO_ID = ["queueAddEndpoint", "queueTarget", "videoId"] NAVIGATION_PLAYLIST_ID = ["navigationEndpoint", "watchEndpoint", "playlistId"] -NAVIGATION_WATCH_PLAYLIST_ID = ["navigationEndpoint", "watchPlaylistEndpoint", "playlistId"] +WATCH_PID = ["watchPlaylistEndpoint", "playlistId"] +NAVIGATION_WATCH_PLAYLIST_ID = ["navigationEndpoint", *WATCH_PID] NAVIGATION_VIDEO_TYPE = [ "watchEndpoint", "watchEndpointMusicSupportedConfigs", @@ -50,6 +52,7 @@ THUMBNAIL = ["thumbnail", "thumbnails"] THUMBNAILS = ["thumbnail", "musicThumbnailRenderer", *THUMBNAIL] THUMBNAIL_RENDERER = ["thumbnailRenderer", "musicThumbnailRenderer", *THUMBNAIL] +THUMBNAIL_OVERLAY = ["thumbnailOverlay", *OVERLAY_RENDERER, "playNavigationEndpoint", *WATCH_PID] THUMBNAIL_CROPPED = ["thumbnail", "croppedSquareThumbnailRenderer", *THUMBNAIL] FEEDBACK_TOKEN = ["feedbackEndpoint", "feedbackToken"] BADGE_PATH = [0, "musicInlineBadgeRenderer", "accessibilityData", "accessibilityData", "label"] diff --git a/ytmusicapi/parsers/albums.py b/ytmusicapi/parsers/albums.py index 5861144..b706106 100644 --- a/ytmusicapi/parsers/albums.py +++ b/ytmusicapi/parsers/albums.py @@ -10,7 +10,9 @@ def parse_album_header(response): "title": nav(header, TITLE_TEXT), "type": nav(header, SUBTITLE), "thumbnails": nav(header, THUMBNAIL_CROPPED), + "isExplicit": nav(header, SUBTITLE_BADGE_LABEL, True) is not None, } + if "description" in header: album["description"] = header["description"]["runs"][0]["text"] diff --git a/ytmusicapi/parsers/browsing.py b/ytmusicapi/parsers/browsing.py index eaf518d..7446bf4 100644 --- a/ytmusicapi/parsers/browsing.py +++ b/ytmusicapi/parsers/browsing.py @@ -54,13 +54,23 @@ def parse_content_list(results, parse_func, key=MTRIR): def parse_album(result): return { "title": nav(result, TITLE_TEXT), + "type": nav(result, SUBTITLE), "year": nav(result, SUBTITLE2, True), + "artists": [parse_id_name(x) for x in nav(result, ["subtitle", "runs"]) if "navigationEndpoint" in x], "browseId": nav(result, TITLE + NAVIGATION_BROWSE_ID), + "audioPlaylistId": nav(result, THUMBNAIL_OVERLAY, True), "thumbnails": nav(result, THUMBNAIL_RENDERER), "isExplicit": nav(result, SUBTITLE_BADGE_LABEL, True) is not None, } +def parse_id_name(sub_run): + return { + "id": nav(sub_run, NAVIGATION_BROWSE_ID, True), + "name": nav(sub_run, ["text"], True), + } + + def parse_single(result): return { "title": nav(result, TITLE_TEXT),