From 34e00e2492dcd60818c23dbc19d0af8674b9fe20 Mon Sep 17 00:00:00 2001 From: sherl Date: Sun, 28 Sep 2025 05:02:51 +0200 Subject: [PATCH] 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 --- ythdd_inv_tl.py | 12 ++++++++---- ythdd_struct_parser.py | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ythdd_inv_tl.py b/ythdd_inv_tl.py index 285e55d..6c10ccc 100644 --- a/ythdd_inv_tl.py +++ b/ythdd_inv_tl.py @@ -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) diff --git a/ythdd_struct_parser.py b/ythdd_struct_parser.py index 692fc75..8a5f1e8 100644 --- a/ythdd_struct_parser.py +++ b/ythdd_struct_parser.py @@ -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,