Skip to content

Commit

Permalink
get_playlist: support audio playlists
Browse files Browse the repository at this point in the history
  • Loading branch information
sgvictorino committed Dec 28, 2024
1 parent b86654f commit 0503d3b
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 9 deletions.
2 changes: 2 additions & 0 deletions tests/data/2024_12_get_playlist_audio.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions tests/data/expected_output/2024_12_get_playlist_audio.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "OLAK5uy_n0x1TMX8DL2eli2g_LysCSg-6Nq5YQa1g",
"title": "Revival",
"owned": false,
"trackCount": 19
}
19 changes: 13 additions & 6 deletions tests/mixins/test_playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,25 @@

class TestPlaylists:
@pytest.mark.parametrize(
"test_file",
"test_file, playlist_id",
[
"2024_03_get_playlist.json",
"2024_03_get_playlist_public.json",
"2024_07_get_playlist_collaborative.json",
("2024_03_get_playlist.json", "PLaZPMsuQNCsWn0iVMtGbaUXO6z-EdZaZm"),
("2024_03_get_playlist_public.json", "RDCLAK5uy_lWy02cQBnTVTlwuRauaGKeUDH3L6PXNxI"),
("2024_07_get_playlist_collaborative.json", "PLEUijtLfpCOgI8LNOwiwvq0EJ8HAGj7dT"),
("2024_12_get_playlist_audio.json", "OLAK5uy_n0x1TMX8DL2eli2g_LysCSg-6Nq5YQa1g"),
],
)
def test_get_playlist(self, yt, test_file):
def test_get_playlist(self, yt, test_file, playlist_id):
data_dir = Path(__file__).parent.parent / "data"
with open(data_dir / test_file, encoding="utf8") as f:
mock_response = json.load(f)
with open(data_dir / "expected_output" / test_file, encoding="utf8") as f:
expected_output = json.load(f)

with mock.patch("ytmusicapi.YTMusic._send_request", return_value=mock_response):
playlist = yt.get_playlist("MPREabc")
playlist = yt.get_playlist(playlist_id)
assert playlist_id == playlist["id"]

assert playlist == playlist | expected_output

for thumbnail in playlist.get("thumbnails", []):
Expand Down Expand Up @@ -63,6 +66,10 @@ def test_get_playlist_foreign(self, yt_oauth, playlist_id, tracks_len, related_l
assert "suggestions" not in playlist
assert playlist["owned"] is False

def test_get_large_audio_playlist(self, yt_oauth):
album = yt_oauth.get_playlist("OLAK5uy_noLNRtYnrcRVVO9rOyGMx64XyjVSCz1YU", limit=500)
assert len(album["tracks"]) == 456

def test_get_playlist_empty(self, yt_empty):
with pytest.raises(Exception):
yt_empty.get_playlist("PLABC")
Expand Down
8 changes: 5 additions & 3 deletions ytmusicapi/mixins/playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ def get_playlist(
browseId = "VL" + playlistId if not playlistId.startswith("VL") else playlistId
body = {"browseId": browseId}
endpoint = "browse"
response = self._send_request(endpoint, body)
request_func = lambda additionalParams: self._send_request(endpoint, body, additionalParams)
response = request_func("")

if playlistId.startswith("OLA") or playlistId.startswith("VLOLA"):
return parse_audio_playlist(response, limit, request_func)

header_data = nav(response, [*TWO_COLUMN_RENDERER, *TAB_CONTENT, *SECTION_LIST_ITEM])
section_list = nav(response, [*TWO_COLUMN_RENDERER, "secondaryContents", *SECTION])
Expand Down Expand Up @@ -139,8 +143,6 @@ def get_playlist(

playlist.update(parse_song_runs(nav(header, SUBTITLE_RUNS)[2 + playlist["owned"] * 2 :]))

request_func = lambda additionalParams: self._send_request(endpoint, body, additionalParams)

# suggestions and related are missing e.g. on liked songs
playlist["related"] = []
if "continuations" in section_list:
Expand Down
41 changes: 41 additions & 0 deletions ytmusicapi/parsers/playlists.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import Optional

from ytmusicapi.continuations import *
from ytmusicapi.helpers import sum_total_duration

from ..helpers import to_int
from .songs import *

Expand Down Expand Up @@ -61,6 +64,44 @@ def parse_playlist_header_meta(header: dict[str, Any]) -> dict[str, Any]:
return playlist_meta


def parse_audio_playlist(response: dict, limit: Optional[int], request_func) -> dict[str, Any]:
playlist: dict = {
"owned": False,
"privacy": "PUBLIC",
"description": None,
"views": None,
"duration": None,
"tracks": [],
"thumbnails": [],
"related": [],
}

section_list = nav(response, [*TWO_COLUMN_RENDERER, "secondaryContents", *SECTION])
content_data = nav(section_list, [*CONTENT, "musicPlaylistShelfRenderer"])

playlist["id"] = nav(
content_data, [*CONTENT, MRLIR, *PLAY_BUTTON, "playNavigationEndpoint", *WATCH_PLAYLIST_ID]
)
playlist["trackCount"] = nav(content_data, ["collapsedItemCount"])

playlist["tracks"] = []
if "contents" in content_data:
playlist["tracks"] = parse_playlist_items(content_data["contents"])

parse_func = lambda contents: parse_playlist_items(contents)
if "continuations" in content_data:
playlist["tracks"].extend(
get_continuations(
content_data, "musicPlaylistShelfContinuation", limit, request_func, parse_func
)
)

playlist["title"] = playlist["tracks"][0]["album"]["name"]

playlist["duration_seconds"] = sum_total_duration(playlist)
return playlist


def parse_playlist_items(results, menu_entries: Optional[list[list]] = None, is_album=False):
songs = []
for result in results:
Expand Down

0 comments on commit 0503d3b

Please sign in to comment.