Skip to content

Commit 59b3ca5

Browse files
sgvictorinosigma67
authored andcommitted
search: fix episode parsing
- `parse_top_result`: handle episode data - `parse_search_result`: ensure `resultType` is "episode", not "video" - `parse_search_result`: offset runs to skip the "Episode" specifier
1 parent 39a480e commit 59b3ca5

File tree

3 files changed

+41
-6
lines changed

3 files changed

+41
-6
lines changed

tests/mixins/test_search.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,36 @@ def test_search_filters(self, yt_auth):
112112
assert len(results) >= 3
113113
assert all(item["resultType"] == "episode" for item in results)
114114

115-
def test_search_top_result(self, yt):
115+
def test_search_episode_category(self, yt):
116+
"""Test resultType detection for episodes by searching for a podcast without a filter."""
117+
results = yt.search("Stanford Graduate School of Business")
118+
episode = next(
119+
item
120+
for item in results
121+
if item["category"] == "Episodes" and item["podcast"]["name"] == "Stanford GSB Podcasts"
122+
)
123+
assert episode["resultType"] == "episode"
124+
assert episode["podcast"]["id"] == "MPSPPLxq_lXOUlvQDUNyoBYLkN8aVt5yAwEtG9"
125+
126+
def test_search_top_result_playlist(self, yt):
116127
results = yt.search("fdsfsfsd") # issue 524
117128
assert results[0]["category"] == "Top result"
118129
assert results[0]["resultType"] == "playlist"
119130
assert results[0]["playlistId"].startswith("PL")
120131
assert len(results[0]["author"]) > 0
121132

133+
def test_search_top_result_episode(self, yt):
134+
results = yt.search(
135+
"Stanford GSB Podcasts 124. Making Meetings Meaningful, Pt. 1: How to Structure and Organize More Effective Gatherings"
136+
)
137+
assert results[0]["category"] == "Top result"
138+
assert results[0]["resultType"] == "episode"
139+
assert results[0]["videoId"] == "KNkyHCLOr1o"
140+
assert results[0]["podcast"] == {
141+
"id": "MPSPPLxq_lXOUlvQDUNyoBYLkN8aVt5yAwEtG9",
142+
"name": "Stanford GSB Podcasts",
143+
}
144+
122145
def test_search_uploads(self, config, yt, yt_oauth):
123146
with pytest.raises(Exception, match="No filter can be set when searching uploads"):
124147
yt.search(

ytmusicapi/navigation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
THUMBNAIL = ["thumbnail", "thumbnails"]
6060
THUMBNAILS = ["thumbnail", "musicThumbnailRenderer", *THUMBNAIL]
6161
THUMBNAIL_RENDERER = ["thumbnailRenderer", "musicThumbnailRenderer", *THUMBNAIL]
62-
THUMBNAIL_OVERLAY = ["thumbnailOverlay", *OVERLAY_RENDERER, "playNavigationEndpoint", *WATCH_PID]
62+
THUMBNAIL_OVERLAY_NAVIGATION = ["thumbnailOverlay", *OVERLAY_RENDERER, "playNavigationEndpoint"]
63+
THUMBNAIL_OVERLAY = [*THUMBNAIL_OVERLAY_NAVIGATION, *WATCH_PID]
6364
THUMBNAIL_CROPPED = ["thumbnail", "croppedSquareThumbnailRenderer", *THUMBNAIL]
6465
FEEDBACK_TOKEN = ["feedbackEndpoint", "feedbackToken"]
6566
BADGE_PATH = [0, "musicInlineBadgeRenderer", "accessibilityData", "accessibilityData", "label"]

ytmusicapi/parsers/search.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ def parse_top_result(data: JsonDict, search_result_types: list[str]) -> JsonDict
6767
search_result["title"] = nav(data, TITLE_TEXT)
6868
search_result["author"] = parse_song_artists_runs(nav(data, ["subtitle", "runs"])[2:])
6969

70+
if result_type in ["episode"]:
71+
search_result["videoId"] = nav(data, [*THUMBNAIL_OVERLAY_NAVIGATION, *WATCH_VIDEO_ID])
72+
search_result["videoType"] = nav(data, [*THUMBNAIL_OVERLAY_NAVIGATION, *NAVIGATION_VIDEO_TYPE])
73+
runs = nav(data, SUBTITLE_RUNS)[2:]
74+
search_result["date"] = runs[0]["text"]
75+
search_result["podcast"] = parse_id_name(runs[2])
76+
7077
search_result["thumbnails"] = nav(data, THUMBNAILS, True)
7178
return search_result
7279

@@ -96,7 +103,10 @@ def parse_search_result(
96103
iter(type for prefix, type in mapping.items() if browse_id.startswith(prefix)), None
97104
)
98105
else:
99-
result_type = "song" if video_type == "MUSIC_VIDEO_TYPE_ATV" else "video"
106+
result_type = {
107+
"MUSIC_VIDEO_TYPE_ATV": "song",
108+
"MUSIC_VIDEO_TYPE_PODCAST_EPISODE": "episode",
109+
}.get(video_type or "", "video")
100110

101111
search_result["resultType"] = result_type
102112

@@ -191,12 +201,13 @@ def parse_search_result(
191201

192202
if result_type in ["episode"]:
193203
flex_item = get_flex_column_item(data, 1)
194-
has_date = int(len(nav(flex_item, TEXT_RUNS)) > 1)
204+
runs = nav(flex_item, TEXT_RUNS)[default_offset:]
205+
has_date = int(len(runs) > 1)
195206
search_result["live"] = bool(nav(data, ["badges", 0, "liveBadgeRenderer"], True))
196207
if has_date:
197-
search_result["date"] = nav(flex_item, TEXT_RUN_TEXT)
208+
search_result["date"] = runs[0]["text"]
198209

199-
search_result["podcast"] = parse_id_name(nav(flex_item, ["text", "runs", has_date * 2]))
210+
search_result["podcast"] = parse_id_name(runs[has_date * 2])
200211

201212
search_result["thumbnails"] = nav(data, THUMBNAILS, True)
202213

0 commit comments

Comments
 (0)