| 
27 | 27 | )  | 
28 | 28 | from ..jsinterp import JSInterpreter  | 
29 | 29 | from ..utils import (  | 
 | 30 | +    bug_reports_message,  | 
30 | 31 |     clean_html,  | 
31 | 32 |     dict_get,  | 
32 | 33 |     error_to_compat_str,  | 
 | 
65 | 66 |     url_or_none,  | 
66 | 67 |     urlencode_postdata,  | 
67 | 68 |     urljoin,  | 
 | 69 | +    variadic,  | 
68 | 70 | )  | 
69 | 71 | 
 
  | 
70 | 72 | 
 
  | 
@@ -460,6 +462,26 @@ def _extract_video(self, renderer):  | 
460 | 462 |             'uploader': uploader,  | 
461 | 463 |         }  | 
462 | 464 | 
 
  | 
 | 465 | +    @staticmethod  | 
 | 466 | +    def _extract_thumbnails(data, *path_list, **kw_final_key):  | 
 | 467 | +        """  | 
 | 468 | +        Extract thumbnails from thumbnails dict  | 
 | 469 | +        @param path_list: path list to level that contains 'thumbnails' key  | 
 | 470 | +        """  | 
 | 471 | +        final_key = kw_final_key.get('final_key', 'thumbnails')  | 
 | 472 | + | 
 | 473 | +        return traverse_obj(data, ((  | 
 | 474 | +            tuple(variadic(path) + (final_key, Ellipsis)  | 
 | 475 | +                  for path in path_list or [()])), {  | 
 | 476 | +            'url': ('url', T(url_or_none),  | 
 | 477 | +                    # Sometimes youtube gives a wrong thumbnail URL. See:  | 
 | 478 | +                    # https://github.com/yt-dlp/yt-dlp/issues/233  | 
 | 479 | +                    # https://github.com/ytdl-org/youtube-dl/issues/28023  | 
 | 480 | +                    T(lambda u: update_url(u, query=None) if u and 'maxresdefault' in u else u)),  | 
 | 481 | +            'height': ('height', T(int_or_none)),  | 
 | 482 | +            'width': ('width', T(int_or_none)),  | 
 | 483 | +        }, T(lambda t: t if t.get('url') else None)))  | 
 | 484 | + | 
463 | 485 |     def _search_results(self, query, params):  | 
464 | 486 |         data = {  | 
465 | 487 |             'context': {  | 
@@ -3183,8 +3205,12 @@ def _get_text(r, k):  | 
3183 | 3205 |             expected_type=txt_or_none)  | 
3184 | 3206 | 
 
  | 
3185 | 3207 |     def _grid_entries(self, grid_renderer):  | 
3186 |  | -        for item in grid_renderer['items']:  | 
3187 |  | -            if not isinstance(item, dict):  | 
 | 3208 | +        for item in traverse_obj(grid_renderer, ('items', Ellipsis, T(dict))):  | 
 | 3209 | +            lockup_view_model = traverse_obj(item, ('lockupViewModel', T(dict)))  | 
 | 3210 | +            if lockup_view_model:  | 
 | 3211 | +                entry = self._extract_lockup_view_model(lockup_view_model)  | 
 | 3212 | +                if entry:  | 
 | 3213 | +                    yield entry  | 
3188 | 3214 |                 continue  | 
3189 | 3215 |             renderer = self._extract_grid_item_renderer(item)  | 
3190 | 3216 |             if not isinstance(renderer, dict):  | 
@@ -3268,6 +3294,25 @@ def _playlist_entries(self, video_list_renderer):  | 
3268 | 3294 |                 continue  | 
3269 | 3295 |             yield self._extract_video(renderer)  | 
3270 | 3296 | 
 
  | 
 | 3297 | +    def _extract_lockup_view_model(self, view_model):  | 
 | 3298 | +        content_id = view_model.get('contentId')  | 
 | 3299 | +        if not content_id:  | 
 | 3300 | +            return  | 
 | 3301 | +        content_type = view_model.get('contentType')  | 
 | 3302 | +        if content_type not in ('LOCKUP_CONTENT_TYPE_PLAYLIST', 'LOCKUP_CONTENT_TYPE_PODCAST'):  | 
 | 3303 | +            self.report_warning(  | 
 | 3304 | +                'Unsupported lockup view model content type "{0}"{1}'.format(content_type, bug_reports_message()), only_once=True)  | 
 | 3305 | +            return  | 
 | 3306 | +        return merge_dicts(self.url_result(  | 
 | 3307 | +            update_url_query('https://www.youtube.com/playlist', {'list': content_id}),  | 
 | 3308 | +            ie=YoutubeTabIE, video_id=content_id), {  | 
 | 3309 | +                'title': traverse_obj(view_model, (  | 
 | 3310 | +                    'metadata', 'lockupMetadataViewModel', 'title', 'content', T(compat_str))),  | 
 | 3311 | +                'thumbnails': self._extract_thumbnails(view_model, (  | 
 | 3312 | +                    'contentImage', 'collectionThumbnailViewModel', 'primaryThumbnail',  | 
 | 3313 | +                    'thumbnailViewModel', 'image'), final_key='sources'),  | 
 | 3314 | +        })  | 
 | 3315 | + | 
3271 | 3316 |     def _video_entry(self, video_renderer):  | 
3272 | 3317 |         video_id = video_renderer.get('videoId')  | 
3273 | 3318 |         if video_id:  | 
 | 
0 commit comments