mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-30 22:25:19 +00:00 
			
		
		
		
	| @@ -24,6 +24,8 @@ from ..utils import ( | ||||
|     parse_iso8601, | ||||
|     parse_qs, | ||||
|     qualities, | ||||
|     str_or_none, | ||||
|     traverse_obj, | ||||
|     try_get, | ||||
|     unified_timestamp, | ||||
|     update_url_query, | ||||
| @@ -52,6 +54,7 @@ class TwitchBaseIE(InfoExtractor): | ||||
|         'VideoAccessToken_Clip': '36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11', | ||||
|         'VideoPreviewOverlay': '3006e77e51b128d838fa4e835723ca4dc9a05c5efd4466c1085215c6e437e65c', | ||||
|         'VideoMetadata': '226edb3e692509f727fd56821f5653c05740242c82b0388883e0c0e75dcbf687', | ||||
|         'VideoPlayer_ChapterSelectButtonVideo': '8d2793384aac3773beab5e59bd5d6f585aedb923d292800119e03d40cd0f9b41', | ||||
|     } | ||||
|  | ||||
|     def _real_initialize(self): | ||||
| @@ -249,6 +252,38 @@ class TwitchVodIE(TwitchBaseIE): | ||||
|     }, { | ||||
|         'url': 'https://player.twitch.tv/?video=480452374', | ||||
|         'only_matching': True, | ||||
|     }, { | ||||
|         'url': 'https://www.twitch.tv/videos/635475444', | ||||
|         'info_dict': { | ||||
|             'id': 'v635475444', | ||||
|             'ext': 'mp4', | ||||
|             'title': 'Riot Games', | ||||
|             'duration': 11643, | ||||
|             'uploader': 'Riot Games', | ||||
|             'uploader_id': 'riotgames', | ||||
|             'timestamp': 1590770569, | ||||
|             'upload_date': '20200529', | ||||
|             'chapters': [ | ||||
|                 { | ||||
|                     'start_time': 0, | ||||
|                     'end_time': 573, | ||||
|                     'title': 'League of Legends' | ||||
|                 }, | ||||
|                 { | ||||
|                     'start_time': 573, | ||||
|                     'end_time': 3922, | ||||
|                     'title': 'Legends of Runeterra' | ||||
|                 }, | ||||
|                 { | ||||
|                     'start_time': 3922, | ||||
|                     'end_time': 11643, | ||||
|                     'title': 'Art' | ||||
|                 } | ||||
|             ], | ||||
|         }, | ||||
|         'params': { | ||||
|             'skip_download': True | ||||
|         } | ||||
|     }] | ||||
|  | ||||
|     def _download_info(self, item_id): | ||||
| @@ -259,16 +294,24 @@ class TwitchVodIE(TwitchBaseIE): | ||||
|                     'channelLogin': '', | ||||
|                     'videoID': item_id, | ||||
|                 }, | ||||
|             }, { | ||||
|                 'operationName': 'VideoPlayer_ChapterSelectButtonVideo', | ||||
|                 'variables': { | ||||
|                     'includePrivate': False, | ||||
|                     'videoID': item_id, | ||||
|                 }, | ||||
|             }], | ||||
|             'Downloading stream metadata GraphQL')[0]['data'] | ||||
|         video = data.get('video') | ||||
|             'Downloading stream metadata GraphQL') | ||||
|  | ||||
|         video = traverse_obj(data, (0, 'data', 'video')) | ||||
|         video['moments'] = traverse_obj(data, (1, 'data', 'video', 'moments', 'edges', ..., 'node')) | ||||
|  | ||||
|         if video is None: | ||||
|             raise ExtractorError( | ||||
|                 'Video %s does not exist' % item_id, expected=True) | ||||
|         return self._extract_info_gql(video, item_id) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _extract_info(info): | ||||
|     def _extract_info(self, info): | ||||
|         status = info.get('status') | ||||
|         if status == 'recording': | ||||
|             is_live = True | ||||
| @@ -304,8 +347,22 @@ class TwitchVodIE(TwitchBaseIE): | ||||
|             'is_live': is_live, | ||||
|         } | ||||
|  | ||||
|     @staticmethod | ||||
|     def _extract_info_gql(info, item_id): | ||||
|     def _extract_moments(self, info, item_id): | ||||
|         for moment in info.get('moments') or []: | ||||
|             start_time = int_or_none(moment.get('positionMilliseconds'), 1000) | ||||
|             duration = int_or_none(moment.get('durationMilliseconds'), 1000) | ||||
|             name = str_or_none(moment.get('description')) | ||||
|  | ||||
|             if start_time is None or duration is None: | ||||
|                 self.report_warning(f'Important chapter information missing for chapter {name}', item_id) | ||||
|                 continue | ||||
|             yield { | ||||
|                 'start_time': start_time, | ||||
|                 'end_time': start_time + duration, | ||||
|                 'title': name, | ||||
|             } | ||||
|  | ||||
|     def _extract_info_gql(self, info, item_id): | ||||
|         vod_id = info.get('id') or item_id | ||||
|         # id backward compatibility for download archives | ||||
|         if vod_id[0] != 'v': | ||||
| @@ -314,6 +371,7 @@ class TwitchVodIE(TwitchBaseIE): | ||||
|         if thumbnail: | ||||
|             for p in ('width', 'height'): | ||||
|                 thumbnail = thumbnail.replace('{%s}' % p, '0') | ||||
|  | ||||
|         return { | ||||
|             'id': vod_id, | ||||
|             'title': info.get('title') or 'Untitled Broadcast', | ||||
| @@ -324,6 +382,7 @@ class TwitchVodIE(TwitchBaseIE): | ||||
|             'uploader_id': try_get(info, lambda x: x['owner']['login'], compat_str), | ||||
|             'timestamp': unified_timestamp(info.get('publishedAt')), | ||||
|             'view_count': int_or_none(info.get('viewCount')), | ||||
|             'chapters': list(self._extract_moments(info, item_id)), | ||||
|         } | ||||
|  | ||||
|     def _real_extract(self, url): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 mpeter50
					mpeter50