diff --git a/yt_dlp/extractor/youtube/_tab.py b/yt_dlp/extractor/youtube/_tab.py index 197231daff..ab5fa8ddab 100644 --- a/yt_dlp/extractor/youtube/_tab.py +++ b/yt_dlp/extractor/youtube/_tab.py @@ -333,20 +333,58 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): only_once=True) return + lockup_mdvm = traverse_obj(view_model, ('metadata', 'lockupMetadataViewModel', {dict})) + content_mdvm = traverse_obj(lockup_mdvm, ('metadata', 'contentMetadataViewModel', {dict})) + + thumbnail_badge_view_models = traverse_obj(view_model, ( + 'contentImage', 'thumbnailViewModel', 'overlays', ..., ( + ('thumbnailBottomOverlayViewModel', 'badges'), + ('thumbnailOverlayBadgeViewModel', 'thumbnailBadges'), + ), ..., 'thumbnailBadgeViewModel', {dict})) + duration_text = traverse_obj(thumbnail_badge_view_models, (..., 'text', {str.lower}, any)) + thumbnail_badge_styles = traverse_obj(thumbnail_badge_view_models, (..., 'badgeStyle', {str})) + + channel_info = traverse_obj(content_mdvm, ( + 'metadataRows', ..., 'metadataParts', + lambda _, v: v['text']['commandRuns'][0]['onTap']['innertubeCommand']['browseEndpoint']['browseId'], + 'text', any, { + 'channel': ('content', {str}), + 'channel_id': ('commandRuns', 0, 'onTap', 'innertubeCommand', 'browseEndpoint', 'browseId', {self.ucid_or_none}), + 'uploader': ('content', {str}), + 'uploader_id': ('commandRuns', 0, 'onTap', 'innertubeCommand', 'browseEndpoint', 'canonicalBaseUrl', {self.handle_from_url}), + })) + + views_and_time = traverse_obj(content_mdvm, ( + 'metadataRows', lambda _, v: 'accessibilityLabel' in v['metadataParts'][-1], + 'metadataParts', ...)) + relative_time_text = traverse_obj(views_and_time, (-1, 'text', 'content', {str.lower})) + + badge_styles = traverse_obj(content_mdvm, ( + 'metadataRows', ..., 'badges', ..., 'badgeViewModel', 'badgeStyle', {str})) + return self.url_result( url, ie, content_id, - title=traverse_obj(view_model, ( - 'metadata', 'lockupMetadataViewModel', 'title', 'content', {str})), + title=traverse_obj(lockup_mdvm, ('title', 'content', {str})), thumbnails=self._extract_thumbnails(view_model, ( 'contentImage', *thumb_keys, 'thumbnailViewModel', 'image'), final_key='sources'), - duration=traverse_obj(view_model, ( - 'contentImage', 'thumbnailViewModel', 'overlays', ..., - (('thumbnailBottomOverlayViewModel', 'badges'), ('thumbnailOverlayBadgeViewModel', 'thumbnailBadges')), - ..., 'thumbnailBadgeViewModel', 'text', {parse_duration}, any)), - timestamp=(traverse_obj(view_model, ( - 'metadata', 'lockupMetadataViewModel', 'metadata', 'contentMetadataViewModel', 'metadataRows', - ..., 'metadataParts', ..., 'text', 'content', {lambda t: self._parse_time_text(t, report_failure=False)}, any)) - if self._configuration_arg('approximate_date', ie_key=YoutubeTabIE) else None)) + duration=parse_duration(duration_text), + view_count=( + traverse_obj(views_and_time, (0, 'text', 'content', {parse_count})) + # view_count isn't always available; only extract if this metadataRow is 2 metadataParts + if len(views_and_time) == 2 else None), + timestamp=( + self._parse_time_text(relative_time_text, report_failure=False) + if self._configuration_arg('approximate_date', ie_key=YoutubeTabIE) else None), + live_status=( + 'is_upcoming' if duration_text == 'upcoming' + else 'is_live' if 'THUMBNAIL_OVERLAY_BADGE_STYLE_LIVE' in thumbnail_badge_styles + else 'was_live' if relative_time_text and 'streamed' in relative_time_text + else None), + # XXX: We cannot assume 'public' since we have no way to differentiate from 'unlisted' + availability=self._availability(needs_subscription='BADGE_MEMBERS_ONLY' in badge_styles), + channel_url=format_field(channel_info, 'channel_id', 'https://www.youtube.com/channel/%s', default=None), + uploader_url=format_field(channel_info, 'uploader_id', 'https://www.youtube.com/%s', default=None), + **channel_info) def _rich_entries(self, rich_grid_renderer): if lockup_view_model := traverse_obj(rich_grid_renderer, ('content', 'lockupViewModel', {dict})): @@ -1588,7 +1626,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor): 'playlist_count': 50, 'expected_warnings': ['YouTube Music is not directly supported'], }, { - # TODO: investigate test failing on differing channel*/uploader*/view_count + # YoutubeTab_25: use to test _extract_lockup_view_model 'note': 'unlisted single video playlist', 'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_', 'info_dict': {