X-Git-Url: https://jfr.im/git/yt-dlp.git/blobdiff_plain/86e5f3ed2e6e71eb81ea4c9e26288f16119ffd0c..f2816634e3be88fe158b342ee33918de3c272a54:/yt_dlp/extractor/mlb.py diff --git a/yt_dlp/extractor/mlb.py b/yt_dlp/extractor/mlb.py index 5fb97083a..d715b9789 100644 --- a/yt_dlp/extractor/mlb.py +++ b/yt_dlp/extractor/mlb.py @@ -1,11 +1,15 @@ import re +import urllib.parse +import uuid from .common import InfoExtractor from ..utils import ( determine_ext, int_or_none, + join_nonempty, parse_duration, parse_iso8601, + traverse_obj, try_get, ) @@ -50,7 +54,6 @@ def _real_extract(self, url): 'width': int(mobj.group(1)), }) formats.append(f) - self._sort_formats(formats) thumbnails = [] for cut in (try_get(feed, lambda x: x['image']['cuts'], list) or []): @@ -92,6 +95,10 @@ class MLBIE(MLBBaseIE): (?P\d+) ) ''' + _EMBED_REGEX = [ + r']+?src=(["\'])(?Phttps?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1', + r'data-video-link=["\'](?Phttp://m\.mlb\.com/video/[^"\']+)', + ] _TESTS = [ { 'url': 'https://www.mlb.com/mariners/video/ackleys-spectacular-catch/c-34698933', @@ -263,3 +270,110 @@ def _download_video_data(self, display_id): } }''' % display_id, })['data']['mediaPlayback'][0] + + +class MLBTVIE(InfoExtractor): + _VALID_URL = r'https?://(?:www\.)?mlb\.com/tv/g(?P\d{6})' + _NETRC_MACHINE = 'mlb' + + _TESTS = [{ + 'url': 'https://www.mlb.com/tv/g661581/vee2eff5f-a7df-4c20-bdb4-7b926fa12638', + 'info_dict': { + 'id': '661581', + 'ext': 'mp4', + 'title': '2022-07-02 - St. Louis Cardinals @ Philadelphia Phillies', + }, + 'params': { + 'skip_download': True, + }, + }] + _access_token = None + + def _real_initialize(self): + if not self._access_token: + self.raise_login_required( + 'All videos are only available to registered users', method='password') + + def _perform_login(self, username, password): + data = f'grant_type=password&username={urllib.parse.quote(username)}&password={urllib.parse.quote(password)}&scope=openid offline_access&client_id=0oa3e1nutA1HLzAKG356' + access_token = self._download_json( + 'https://ids.mlb.com/oauth2/aus1m088yK07noBfh356/v1/token', None, + headers={ + 'User-Agent': 'okhttp/3.12.1', + 'Content-Type': 'application/x-www-form-urlencoded' + }, data=data.encode())['access_token'] + + entitlement = self._download_webpage( + f'https://media-entitlement.mlb.com/api/v3/jwt?os=Android&appname=AtBat&did={str(uuid.uuid4())}', None, + headers={ + 'User-Agent': 'okhttp/3.12.1', + 'Authorization': f'Bearer {access_token}' + }) + + data = f'grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token={entitlement}&subject_token_type=urn:ietf:params:oauth:token-type:jwt&platform=android-tv' + self._access_token = self._download_json( + 'https://us.edge.bamgrid.com/token', None, + headers={ + 'Accept': 'application/json', + 'Authorization': 'Bearer bWxidHYmYW5kcm9pZCYxLjAuMA.6LZMbH2r--rbXcgEabaDdIslpo4RyZrlVfWZhsAgXIk', + 'Content-Type': 'application/x-www-form-urlencoded' + }, data=data.encode())['access_token'] + + def _real_extract(self, url): + video_id = self._match_id(url) + airings = self._download_json( + f'https://search-api-mlbtv.mlb.com/svc/search/v2/graphql/persisted/query/core/Airings?variables=%7B%22partnerProgramIds%22%3A%5B%22{video_id}%22%5D%2C%22applyEsniMediaRightsLabels%22%3Atrue%7D', + video_id)['data']['Airings'] + + formats, subtitles = [], {} + for airing in airings: + m3u8_url = self._download_json( + airing['playbackUrls'][0]['href'].format(scenario='browser~csai'), video_id, + headers={ + 'Authorization': self._access_token, + 'Accept': 'application/vnd.media-service+json; version=2' + })['stream']['complete'] + f, s = self._extract_m3u8_formats_and_subtitles( + m3u8_url, video_id, 'mp4', m3u8_id=join_nonempty(airing.get('feedType'), airing.get('feedLanguage'))) + formats.extend(f) + self._merge_subtitles(s, target=subtitles) + + return { + 'id': video_id, + 'title': traverse_obj(airings, (..., 'titles', 0, 'episodeName'), get_all=False), + 'is_live': traverse_obj(airings, (..., 'mediaConfig', 'productType'), get_all=False) == 'LIVE', + 'formats': formats, + 'subtitles': subtitles, + 'http_headers': {'Authorization': f'Bearer {self._access_token}'}, + } + + +class MLBArticleIE(InfoExtractor): + _VALID_URL = r'https?://www\.mlb\.com/news/(?P[\w-]+)' + _TESTS = [{ + 'url': 'https://www.mlb.com/news/manny-machado-robs-guillermo-heredia-reacts', + 'info_dict': { + 'id': '36db7394-343c-4ea3-b8ca-ead2e61bca9a', + 'title': 'Machado\'s grab draws hilarious irate reaction', + 'modified_timestamp': 1675888370, + 'description': 'md5:a19d4eb0487b2cb304e9a176f6b67676', + 'modified_date': '20230208', + }, + 'playlist_mincount': 2, + }] + + def _real_extract(self, url): + display_id = self._match_id(url) + webpage = self._download_webpage(url, display_id) + apollo_cache_json = self._search_json(r'window\.initState\s*=', webpage, 'window.initState', display_id)['apolloCache'] + + content_real_info = traverse_obj( + apollo_cache_json, ('ROOT_QUERY', lambda k, _: k.startswith('getArticle')), get_all=False) + + return self.playlist_from_matches( + traverse_obj(content_real_info, ('parts', lambda _, v: v['__typename'] == 'Video' or v['type'] == 'video')), + getter=lambda x: f'https://www.mlb.com/video/{x["slug"]}', + ie=MLBVideoIE, playlist_id=content_real_info.get('translationId'), + title=self._html_search_meta('og:title', webpage), + description=content_real_info.get('summary'), + modified_timestamp=parse_iso8601(content_real_info.get('lastUpdatedDate')))