From 80eb0bd9b94106df9e1e5ac288def6e239937329 Mon Sep 17 00:00:00 2001 From: coletdjnz Date: Thu, 22 Sep 2022 05:39:02 +0000 Subject: [PATCH] [extractor/youtube] Add support for Shorts audio pivot feed (#4932) This feed shows Shorts using the audio of a given video. ytshortsap: prefix can be used as a shortcut until YouTube implements an official view. Closes #4911 Authored by: coletdjnz --- yt_dlp/extractor/_extractors.py | 1 + yt_dlp/extractor/youtube.py | 41 +++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 43e2f93d35..e247871361 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -21,6 +21,7 @@ from .youtube import ( # Youtube is moved to the top to improve performance YoutubeYtBeIE, YoutubeYtUserIE, YoutubeWatchLaterIE, + YoutubeShortsAudioPivotIE ) from .abc import ( diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index ac1a5f2109..2afb993d01 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -4327,8 +4327,8 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): yield self._extract_video(renderer) def _rich_entries(self, rich_grid_renderer): - renderer = try_get( - rich_grid_renderer, lambda x: x['content']['videoRenderer'], dict) or {} + renderer = traverse_obj( + rich_grid_renderer, ('content', ('videoRenderer', 'reelItemRenderer')), get_all=False) or {} video_id = renderer.get('videoId') if not video_id: return @@ -5640,6 +5640,16 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor): 'playlist_mincount': 1, 'params': {'extractor_args': {'youtube': {'lang': ['ja']}}}, 'expected_warnings': ['Preferring "ja"'], + }, { + # shorts audio pivot for 2GtVksBMYFM. + 'url': 'https://www.youtube.com/feed/sfv_audio_pivot?bp=8gUrCikSJwoLMkd0VmtzQk1ZRk0SCzJHdFZrc0JNWUZNGgsyR3RWa3NCTVlGTQ==', + 'info_dict': { + 'id': 'sfv_audio_pivot', + 'title': 'sfv_audio_pivot', + 'tags': [], + }, + 'playlist_mincount': 50, + }] @classmethod @@ -6307,6 +6317,33 @@ class YoutubeStoriesIE(InfoExtractor): ie=YoutubeTabIE, video_id=playlist_id) +class YoutubeShortsAudioPivotIE(InfoExtractor): + IE_DESC = 'YouTube Shorts audio pivot (Shorts using audio of a given video); "ytshortsap:" prefix' + IE_NAME = 'youtube:shorts:pivot:audio' + _VALID_URL = f'(?x)^ytshortsap:{YoutubeIE._VALID_URL[5:]}' + _TESTS = [{ + 'url': 'ytshortsap:https://www.youtube.com/shorts/Lyj-MZSAA9o?feature=share', + 'only_matching': True, + }, { + 'url': 'ytshortsap:Lyj-MZSAA9o', + 'only_matching': True, + }] + + @staticmethod + def _generate_audio_pivot_params(video_id): + """ + Generates sfv_audio_pivot browse params for this video id + """ + pb_params = b'\xf2\x05+\n)\x12\'\n\x0b%b\x12\x0b%b\x1a\x0b%b' % ((video_id.encode(),) * 3) + return urllib.parse.quote(base64.b64encode(pb_params).decode()) + + def _real_extract(self, url): + video_id = self._match_id(url) + return self.url_result( + f'https://www.youtube.com/feed/sfv_audio_pivot?bp={self._generate_audio_pivot_params(video_id)}', + ie=YoutubeTabIE) + + class YoutubeTruncatedURLIE(InfoExtractor): IE_NAME = 'youtube:truncated_url' IE_DESC = False # Do not list