Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions tests/parsers/test_thumbnails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Parsers must tolerate items without thumbnail data (see #977)."""

import pytest

from ytmusicapi.parsers.browsing import (
parse_album,
parse_related_artist,
parse_single,
parse_song,
parse_watch_playlist,
)
from ytmusicapi.parsers.explore import parse_chart_playlist
from ytmusicapi.parsers.library import parse_albums
from ytmusicapi.parsers.playlists import parse_playlist_header_meta
from ytmusicapi.parsers.podcasts import parse_base_header, parse_podcast
from ytmusicapi.parsers.watch import parse_watch_track


def _title(text: str = "Title", browse_id: str | None = None, video_id: str | None = None) -> dict:
run: dict = {"text": text}
if browse_id:
run["navigationEndpoint"] = {"browseEndpoint": {"browseId": browse_id}}
if video_id:
run["navigationEndpoint"] = {"watchEndpoint": {"videoId": video_id}}
return {"title": {"runs": [run]}}


def _subtitle(*texts: str) -> dict:
return {"subtitle": {"runs": [{"text": text} for text in texts]}}


@pytest.mark.parametrize(
("parse_func", "item"),
[
(parse_album, {**_title(browse_id="MPREb_1"), **_subtitle("Album", " • ", "2024")}),
(parse_single, {**_title(browse_id="MPREb_1"), **_subtitle("2024")}),
(
parse_song,
{
**_title(),
**_subtitle("Artist"),
"navigationEndpoint": {"watchEndpoint": {"videoId": "videoId1"}},
},
),
(parse_related_artist, {**_title(browse_id="UC_1"), **_subtitle("1M subscribers")}),
(
parse_watch_playlist,
{**_title(), "navigationEndpoint": {"watchPlaylistEndpoint": {"playlistId": "RD1"}}},
),
(parse_chart_playlist, _title(browse_id="VLPL1")),
(parse_podcast, {**_title(browse_id="MPSP1"), **_subtitle("Channel")}),
(parse_base_header, {**_title(), "straplineTextOne": {}}),
(parse_playlist_header_meta, {**_title(), "secondSubtitle": {}}),
],
)
def test_missing_thumbnails(parse_func, item):
assert parse_func(item)["thumbnails"] is None


def test_missing_thumbnails_library_albums():
item = {"musicTwoRowItemRenderer": {**_title(browse_id="MPREb_1"), "subtitle": {}}}

assert parse_albums([item])[0]["thumbnails"] is None


def test_missing_thumbnail_watch_track():
track = {
"videoId": "videoId1",
**_title(),
"menu": {"menuRenderer": {"items": []}},
"longBylineText": {"runs": [{"text": "Artist"}]},
}

assert parse_watch_track(track)["thumbnail"] is None
2 changes: 1 addition & 1 deletion ytmusicapi/mixins/podcasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def get_channel(self, channelId: str) -> JsonDict:

channel = {
"title": nav(response, [*HEADER_MUSIC_VISUAL, *TITLE_TEXT]),
"thumbnails": nav(response, [*HEADER_MUSIC_VISUAL, *THUMBNAILS]),
"thumbnails": nav(response, [*HEADER_MUSIC_VISUAL, *THUMBNAILS], True),
}

results = nav(response, SINGLE_COLUMN_TAB + SECTION_LIST)
Expand Down
4 changes: 2 additions & 2 deletions ytmusicapi/parsers/albums.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def parse_album_header(response: JsonDict) -> JsonDict:
album = {
"title": nav(header, TITLE_TEXT),
"type": nav(header, SUBTITLE),
"thumbnails": nav(header, THUMBNAIL_CROPPED),
"thumbnails": nav(header, THUMBNAIL_CROPPED, True),
"isExplicit": nav(header, SUBTITLE_BADGE_LABEL, True) is not None,
}

Expand Down Expand Up @@ -45,7 +45,7 @@ def parse_album_header_2024(response: JsonDict) -> JsonDict:
album = {
"title": nav(header, TITLE_TEXT),
"type": nav(header, SUBTITLE),
"thumbnails": nav(header, THUMBNAILS),
"thumbnails": nav(header, THUMBNAILS, True),
"isExplicit": nav(header, SUBTITLE_BADGE_LABEL, True) is not None,
}
description, description_runs = parse_description_runs(
Expand Down
12 changes: 6 additions & 6 deletions ytmusicapi/parsers/browsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def parse_album(result: JsonDict) -> JsonDict:
],
"browseId": nav(result, TITLE + NAVIGATION_BROWSE_ID),
"audioPlaylistId": parse_album_playlistid_if_exists(nav(result, THUMBNAIL_OVERLAY_NAVIGATION, True)),
"thumbnails": nav(result, THUMBNAIL_RENDERER),
"thumbnails": nav(result, THUMBNAIL_RENDERER, True),
"isExplicit": nav(result, SUBTITLE_BADGE_LABEL, True) is not None,
}

Expand All @@ -102,7 +102,7 @@ def parse_single(result: JsonDict) -> JsonDict:
single = {
"title": nav(result, TITLE_TEXT),
"browseId": nav(result, TITLE + NAVIGATION_BROWSE_ID),
"thumbnails": nav(result, THUMBNAIL_RENDERER),
"thumbnails": nav(result, THUMBNAIL_RENDERER, True),
}

return _parse_album_single_subtitle(result, single)
Expand All @@ -113,7 +113,7 @@ def parse_song(result: JsonDict) -> JsonDict:
"title": nav(result, TITLE_TEXT),
"videoId": nav(result, NAVIGATION_VIDEO_ID),
"playlistId": nav(result, NAVIGATION_PLAYLIST_ID, True),
"thumbnails": nav(result, THUMBNAIL_RENDERER),
"thumbnails": nav(result, THUMBNAIL_RENDERER, True),
}
song.update(parse_song_runs(nav(result, SUBTITLE_RUNS), skip_type_spec=True))
return song
Expand All @@ -125,7 +125,7 @@ def parse_song_flat(data: JsonDict, with_playlist_id: bool = False) -> JsonDict:
"title": nav(columns[0], TEXT_RUN_TEXT),
"videoId": nav(columns[0], TEXT_RUN + NAVIGATION_VIDEO_ID, True),
"videoType": nav(data, [*PLAY_BUTTON, "playNavigationEndpoint", *NAVIGATION_VIDEO_TYPE], True),
"thumbnails": nav(data, THUMBNAILS),
"thumbnails": nav(data, THUMBNAILS, True),
"isExplicit": nav(data, BADGE_LABEL, True) is not None,
}

Expand Down Expand Up @@ -208,13 +208,13 @@ def parse_related_artist(data: JsonDict) -> JsonDict:
"title": nav(data, TITLE_TEXT),
"browseId": nav(data, TITLE + NAVIGATION_BROWSE_ID),
"subscribers": subscribers,
"thumbnails": nav(data, THUMBNAIL_RENDERER),
"thumbnails": nav(data, THUMBNAIL_RENDERER, True),
}


def parse_watch_playlist(data: JsonDict) -> JsonDict:
return {
"title": nav(data, TITLE_TEXT),
"playlistId": nav(data, NAVIGATION_WATCH_PLAYLIST_ID),
"thumbnails": nav(data, THUMBNAIL_RENDERER),
"thumbnails": nav(data, THUMBNAIL_RENDERER, True),
}
4 changes: 2 additions & 2 deletions ytmusicapi/parsers/explore.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def parse_chart_playlist(data: JsonDict) -> JsonDict:
return {
"title": nav(data, TITLE_TEXT),
"playlistId": nav(data, TITLE + NAVIGATION_BROWSE_ID)[2:],
"thumbnails": nav(data, THUMBNAIL_RENDERER),
"thumbnails": nav(data, THUMBNAIL_RENDERER, True),
}


Expand All @@ -47,7 +47,7 @@ def parse_chart_artist(data: JsonDict) -> JsonDict:
"title": nav(get_flex_column_item(data, 0), TEXT_RUN_TEXT),
"browseId": nav(data, NAVIGATION_BROWSE_ID),
"subscribers": subscribers,
"thumbnails": nav(data, THUMBNAILS),
"thumbnails": nav(data, THUMBNAILS, True),
}
parsed.update(parse_ranking(data, none_if_absent=True))
return parsed
Expand Down
2 changes: 1 addition & 1 deletion ytmusicapi/parsers/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def parse_albums(results: JsonList) -> JsonList:
album["browseId"] = nav(data, TITLE + NAVIGATION_BROWSE_ID)
album["playlistId"] = nav(data, MENU_PLAYLIST_ID, none_if_absent=True)
album["title"] = nav(data, TITLE_TEXT)
album["thumbnails"] = nav(data, THUMBNAIL_RENDERER)
album["thumbnails"] = nav(data, THUMBNAIL_RENDERER, True)

if "runs" in data["subtitle"]:
album["type"] = nav(data, SUBTITLE)
Expand Down
2 changes: 1 addition & 1 deletion ytmusicapi/parsers/playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def parse_playlist_header_meta(header: JsonDict) -> JsonDict:
"duration": None,
"trackCount": None,
"title": "".join([run["text"] for run in header.get("title", {}).get("runs", [])]),
"thumbnails": nav(header, THUMBNAILS),
"thumbnails": nav(header, THUMBNAILS, True),
}
if "facepile" in header:
avatar_renderer = nav(header, ["facepile", "avatarStackViewModel", "rendererContext"])
Expand Down
8 changes: 4 additions & 4 deletions ytmusicapi/parsers/podcasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def parse_base_header(header: JsonDict) -> JsonDict:
return {
"author": author if author["name"] else None,
"title": nav(header, TITLE_TEXT),
"thumbnails": nav(header, THUMBNAILS),
"thumbnails": nav(header, THUMBNAILS, True),
}


Expand Down Expand Up @@ -107,7 +107,7 @@ def parse_episode_header(header: JsonDict) -> JsonDict:

def parse_episode(data: JsonDict) -> JsonDict:
"""Parses a single episode under "Episodes" on a channel page or on a podcast page"""
thumbnails = nav(data, THUMBNAILS)
thumbnails = nav(data, THUMBNAILS, True)
date = nav(data, SUBTITLE, True)
duration = nav(data, ["playbackProgress", *PROGRESS_RENDERER, *DURATION_TEXT], True)
title = nav(data, TITLE_TEXT)
Expand Down Expand Up @@ -138,7 +138,7 @@ def parse_episode_flat(data: JsonDict) -> JsonDict:
"playlistId": nav(data, [*PLAY_BUTTON, "playNavigationEndpoint", *WATCH_PLAYLIST_ID]),
"videoType": nav(data, [*PLAY_BUTTON, "playNavigationEndpoint", *NAVIGATION_VIDEO_TYPE]),
"date": nav(get_flex_column_item(data, 2), TEXT_RUN_TEXT),
"thumbnails": nav(data, THUMBNAILS),
"thumbnails": nav(data, THUMBNAILS, True),
}


Expand All @@ -149,5 +149,5 @@ def parse_podcast(data: JsonDict) -> JsonDict:
"channel": parse_id_name(nav(data, [*SUBTITLE_RUNS, 0], True)),
"browseId": nav(data, TITLE + NAVIGATION_BROWSE_ID),
"podcastId": nav(data, THUMBNAIL_OVERLAY, True),
"thumbnails": nav(data, THUMBNAIL_RENDERER),
"thumbnails": nav(data, THUMBNAIL_RENDERER, True),
}
2 changes: 1 addition & 1 deletion ytmusicapi/parsers/uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def parse_uploaded_items(results: JsonList) -> JsonList:

title = get_item_text(data, 0)
like = nav(data, MENU_LIKE_STATUS)
thumbnails = nav(data, THUMBNAILS) if "thumbnail" in data else None
thumbnails = nav(data, THUMBNAILS, True)
duration = None
if "fixedColumns" in data:
duration = nav(get_fixed_column_item(data, 0), TEXT_RUN_TEXT)
Expand Down
2 changes: 1 addition & 1 deletion ytmusicapi/parsers/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def parse_watch_track(data: JsonDict) -> JsonDict:
"videoId": data["videoId"],
"title": nav(data, TITLE_TEXT),
"length": nav(data, ["lengthText", "runs", 0, "text"], True),
"thumbnail": nav(data, THUMBNAIL),
"thumbnail": nav(data, THUMBNAIL, True),
"likeStatus": like_status,
"videoType": nav(data, ["navigationEndpoint", *NAVIGATION_VIDEO_TYPE], True),
}
Expand Down
Loading