Compare commits

..

2 Commits

Author SHA1 Message Date
34e00e2492 fix: handle collaboratively authored videos in playlists and videos
endpoint
currently, the videos endpoint returns the video uploader name,
and not "author1, author2, author3" as is the case for videoRenderer
and playlistVideoRenderer - this might change in the future in order for
the endpoints to return the same data
2025-09-28 05:02:51 +02:00
f63c620541 fix: avatars for artist channels
ensures that we get the renderers, rather than expecting them to always
be the first or second element of primary_results (which is not the case
if the video has for example the "Nearest event" ticketShelfRenderer)
2025-09-27 23:52:50 +02:00
2 changed files with 25 additions and 7 deletions

View File

@@ -379,8 +379,8 @@ def videos(data):
main_results = wdata['ec2']['contents']['twoColumnWatchNextResults']
primary_results = safeTraverse(main_results, ['results', 'results', 'contents'])
# video_primary_renderer = safeTraverse(primary_results, [0, 'videoPrimaryInfoRenderer'])
video_secondary_renderer = safeTraverse(primary_results, [1, 'videoSecondaryInfoRenderer'])
# video_primary_renderer = safeTraverse(primary_results, [..., 'videoPrimaryInfoRenderer'])
video_secondary_renderer = safeTraverse(primary_results, [..., 'videoSecondaryInfoRenderer'])
video_details = safeTraverse(wdata, ['ec1', 'videoDetails'])
microformat = safeTraverse(wdata, ['ec1', 'microformat', 'playerMicroformatRenderer'], default={})
@@ -454,11 +454,15 @@ def videos(data):
author = safeTraverse(video_details, ['author'], default="Unknown Author")
ucid = safeTraverse(video_details, ['channelId'], default="UNKNOWNCHANNELID")
subs = ydata['channel_follower_count']
author_thumbnail = ythdd_extractor.generateChannelAvatarsFromUrl(safeTraverse(video_secondary_renderer, ['owner', 'videoOwnerRenderer', 'thumbnail', 'thumbnails', 0, 'url'], default=DEFAULT_AVATAR))
# so far it seems to be impossible to tell if a channel is verified or not,
# that is - without making another request
author_thumbnail = safeTraverse(video_secondary_renderer, ['owner', 'videoOwnerRenderer', 'thumbnail', 'thumbnails', 0, 'url'])
author_verified = ythdd_extractor.isVerified(safeTraverse(video_secondary_renderer, ['owner', 'videoOwnerRenderer', 'badges', 0], default=[]))
if author_thumbnail is None:
# there might be multiple authors (on a collaborative video)
# if so, then try to extract first channel's (uploader's) avatar
livm = safeTraverse(video_secondary_renderer, ["owner", "videoOwnerRenderer", "attributedTitle", "commandRuns", 0, "onTap", "innertubeCommand", "showDialogCommand", "panelLoadingStrategy", "inlineContent", "dialogViewModel", "customContent", "listViewModel", "listItems"], default=[])
author_thumbnail = safeTraverse(livm, [0, "listItemViewModel", "leadingAccessory", "avatarViewModel", "image", "sources", 0, "url"], default=DEFAULT_AVATAR)
author_verified = author_verified or safeTraverse(livm, [0, "listItemViewModel", "title", "attachmentRuns", 0, "element", "type", "imageType", "image", "sources", 0, "clientResource", "imageName"]) in ("AUDIO_BADGE", "CHECK_CIRCLE_FILLED")
author_thumbnail = ythdd_extractor.generateChannelAvatarsFromUrl(author_thumbnail)
format_streams = []
# adaptive_formats, format_streams = rebuildFormats(adaptive_formats)

View File

@@ -346,7 +346,7 @@ def parseRenderers(entry: dict, context: dict = {}) -> dict:
video_id = safeTraverse(entry, ["playlistVideoRenderer", "videoId"], default="UnknownVideoId")
title = safeTraverse(entry, ["playlistVideoRenderer", "title", "runs", 0, "text"], default="Unknown video title")
author_ucid = safeTraverse(entry, ["playlistVideoRenderer", "shortBylineText", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId"], default="UNKNOWNCHANNELID")
author_ucid = safeTraverse(entry, ["playlistVideoRenderer", "shortBylineText", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId"])
author_name = safeTraverse(entry, ["playlistVideoRenderer", "shortBylineText", "runs", 0, "text"], default="Unknown author")
video_index = int(safeTraverse(entry, ["playlistVideoRenderer", "index", "simpleText"], default="1")) - 1
length = parseLengthFromTimeBadge(safeTraverse(entry, ["playlistVideoRenderer", "lengthText", "simpleText"], default="0:0"))
@@ -357,6 +357,20 @@ def parseRenderers(entry: dict, context: dict = {}) -> dict:
if not published_date:
published_date = "now"
if author_ucid is None:
# likely a collaborative video, let's try
# to fetch the uploader's ucid with that in mind
livm = safeTraverse(entry, ["playlistVideoRenderer", "shortBylineText", "runs", 0, "navigationEndpoint", "showDialogCommand", "panelLoadingStrategy", "inlineContent", "dialogViewModel", "customContent", "listViewModel", "listItems"], default=[])
# name extraction logic the same as in videoRenderer
all_authors = []
for collaborative_author in livm:
collaborative_author_name = safeTraverse(collaborative_author, ["listItemViewModel", "title", "content"])
if collaborative_author_name is not None:
all_authors.append(collaborative_author_name)
if all_authors != []:
author_name = ", ".join(all_authors)
author_ucid = safeTraverse(livm, [0, "listItemViewModel", "title", "commandRuns", 0, "onTap", "innertubeCommand", "browseEndpoint", "browseId"], default="UNKNOWNCHANNELID")
return {
"type": "video",
"title": title,