logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

nfl.py (16180B)


  1. import base64
  2. import json
  3. import re
  4. import time
  5. import uuid
  6. from .anvato import AnvatoIE
  7. from .common import InfoExtractor
  8. from ..utils import (
  9. ExtractorError,
  10. clean_html,
  11. determine_ext,
  12. get_element_by_class,
  13. int_or_none,
  14. make_archive_id,
  15. url_or_none,
  16. urlencode_postdata,
  17. )
  18. from ..utils.traversal import traverse_obj
  19. class NFLBaseIE(InfoExtractor):
  20. _VALID_URL_BASE = r'''(?x)
  21. https?://
  22. (?P<host>
  23. (?:www\.)?
  24. (?:
  25. (?:
  26. nfl|
  27. buffalobills|
  28. miamidolphins|
  29. patriots|
  30. newyorkjets|
  31. baltimoreravens|
  32. bengals|
  33. clevelandbrowns|
  34. steelers|
  35. houstontexans|
  36. colts|
  37. jaguars|
  38. (?:titansonline|tennesseetitans)|
  39. denverbroncos|
  40. (?:kc)?chiefs|
  41. raiders|
  42. chargers|
  43. dallascowboys|
  44. giants|
  45. philadelphiaeagles|
  46. (?:redskins|washingtonfootball)|
  47. chicagobears|
  48. detroitlions|
  49. packers|
  50. vikings|
  51. atlantafalcons|
  52. panthers|
  53. neworleanssaints|
  54. buccaneers|
  55. azcardinals|
  56. (?:stlouis|the)rams|
  57. 49ers|
  58. seahawks
  59. )\.com|
  60. .+?\.clubs\.nfl\.com
  61. )
  62. )/
  63. '''
  64. _VIDEO_CONFIG_REGEX = r'<script[^>]+id="[^"]*video-config-[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}[^"]*"[^>]*>\s*({.+});?\s*</script>'
  65. _ANVATO_PREFIX = 'anvato:GXvEgwyJeWem8KCYXfeoHWknwP48Mboj:'
  66. _CLIENT_DATA = {
  67. 'clientKey': '4cFUW6DmwJpzT9L7LrG3qRAcABG5s04g',
  68. 'clientSecret': 'CZuvCL49d9OwfGsR',
  69. 'deviceId': str(uuid.uuid4()),
  70. 'deviceInfo': base64.b64encode(json.dumps({
  71. 'model': 'desktop',
  72. 'version': 'Chrome',
  73. 'osName': 'Windows',
  74. 'osVersion': '10.0',
  75. }, separators=(',', ':')).encode()).decode(),
  76. 'networkType': 'other',
  77. 'peacockUUID': 'undefined',
  78. }
  79. _ACCOUNT_INFO = {}
  80. _API_KEY = '3_Qa8TkWpIB8ESCBT8tY2TukbVKgO5F6BJVc7N1oComdwFzI7H2L9NOWdm11i_BY9f'
  81. _TOKEN = None
  82. _TOKEN_EXPIRY = 0
  83. def _get_account_info(self):
  84. cookies = self._get_cookies('https://auth-id.nfl.com/')
  85. login_token = traverse_obj(cookies, (
  86. (f'glt_{self._API_KEY}', lambda k, _: k.startswith('glt_')), {lambda x: x.value}), get_all=False)
  87. if not login_token:
  88. self.raise_login_required()
  89. if 'ucid' not in cookies:
  90. raise ExtractorError(
  91. 'Required cookies for the auth-id.nfl.com domain were not found among passed cookies. '
  92. 'If using --cookies, these cookies must be exported along with .nfl.com cookies, '
  93. 'or else try using --cookies-from-browser instead', expected=True)
  94. account = self._download_json(
  95. 'https://auth-id.nfl.com/accounts.getAccountInfo', None,
  96. note='Downloading account info', data=urlencode_postdata({
  97. 'include': 'profile,data',
  98. 'lang': 'en',
  99. 'APIKey': self._API_KEY,
  100. 'sdk': 'js_latest',
  101. 'login_token': login_token,
  102. 'authMode': 'cookie',
  103. 'pageURL': 'https://www.nfl.com/',
  104. 'sdkBuild': traverse_obj(cookies, (
  105. 'gig_canary_ver', {lambda x: x.value.partition('-')[0]}), default='15170'),
  106. 'format': 'json',
  107. }), headers={'Content-Type': 'application/x-www-form-urlencoded'})
  108. self._ACCOUNT_INFO = traverse_obj(account, {
  109. 'signatureTimestamp': 'signatureTimestamp',
  110. 'uid': 'UID',
  111. 'uidSignature': 'UIDSignature',
  112. })
  113. if len(self._ACCOUNT_INFO) != 3:
  114. raise ExtractorError('Failed to retrieve account info with provided cookies', expected=True)
  115. def _get_auth_token(self):
  116. if self._TOKEN and self._TOKEN_EXPIRY > int(time.time() + 30):
  117. return
  118. token = self._download_json(
  119. 'https://api.nfl.com/identity/v3/token%s' % (
  120. '/refresh' if self._ACCOUNT_INFO.get('refreshToken') else ''),
  121. None, headers={'Content-Type': 'application/json'}, note='Downloading access token',
  122. data=json.dumps({**self._CLIENT_DATA, **self._ACCOUNT_INFO}, separators=(',', ':')).encode())
  123. self._TOKEN = token['accessToken']
  124. self._TOKEN_EXPIRY = token['expiresIn']
  125. self._ACCOUNT_INFO['refreshToken'] = token['refreshToken']
  126. def _extract_video(self, mcp_id, is_live=False):
  127. self._get_auth_token()
  128. data = self._download_json(
  129. f'https://api.nfl.com/play/v1/asset/{mcp_id}', mcp_id, headers={
  130. 'Authorization': f'Bearer {self._TOKEN}',
  131. 'Accept': 'application/json',
  132. 'Content-Type': 'application/json',
  133. }, data=json.dumps({'init': True, 'live': is_live}, separators=(',', ':')).encode())
  134. formats, subtitles = self._extract_m3u8_formats_and_subtitles(
  135. data['accessUrl'], mcp_id, 'mp4', m3u8_id='hls')
  136. return {
  137. 'id': mcp_id,
  138. 'formats': formats,
  139. 'subtitles': subtitles,
  140. 'is_live': is_live,
  141. '_old_archive_ids': [make_archive_id(AnvatoIE, mcp_id)],
  142. **traverse_obj(data, ('metadata', {
  143. 'title': ('event', ('def_title', 'friendlyName'), {str}, any),
  144. 'description': ('event', 'def_description', {str}),
  145. 'duration': ('event', 'duration', {int_or_none}),
  146. 'thumbnails': ('thumbnails', ..., 'url', {'url': {url_or_none}}),
  147. })),
  148. }
  149. def _parse_video_config(self, video_config, display_id):
  150. video_config = self._parse_json(video_config, display_id)
  151. is_live = traverse_obj(video_config, ('live', {bool})) or False
  152. item = video_config['playlist'][0]
  153. if mcp_id := item.get('mcpID'):
  154. return self._extract_video(mcp_id, is_live=is_live)
  155. info = {'id': item.get('id') or item['entityId']}
  156. item_url = item['url']
  157. ext = determine_ext(item_url)
  158. if ext == 'm3u8':
  159. info['formats'] = self._extract_m3u8_formats(item_url, info['id'], 'mp4')
  160. else:
  161. info['url'] = item_url
  162. if item.get('audio') is True:
  163. info['vcodec'] = 'none'
  164. thumbnails = None
  165. if image_url := traverse_obj(item, 'imageSrc', 'posterImage', expected_type=url_or_none):
  166. thumbnails = [{
  167. 'url': image_url,
  168. 'ext': determine_ext(image_url, 'jpg'),
  169. }]
  170. info.update({
  171. **traverse_obj(item, {
  172. 'title': ('title', {str}),
  173. 'description': ('description', {clean_html}),
  174. }),
  175. 'is_live': is_live,
  176. 'thumbnails': thumbnails,
  177. })
  178. return info
  179. class NFLIE(NFLBaseIE):
  180. IE_NAME = 'nfl.com'
  181. _VALID_URL = NFLBaseIE._VALID_URL_BASE + r'(?:videos?|listen|audio)/(?P<id>[^/#?&]+)'
  182. _TESTS = [{
  183. 'url': 'https://www.nfl.com/videos/baker-mayfield-s-game-changing-plays-from-3-td-game-week-14',
  184. 'info_dict': {
  185. 'id': '899441',
  186. 'ext': 'mp4',
  187. 'title': "Baker Mayfield's game-changing plays from 3-TD game Week 14",
  188. 'description': 'md5:85e05a3cc163f8c344340f220521136d',
  189. 'thumbnail': r're:https?://.+\.jpg',
  190. 'duration': 157,
  191. '_old_archive_ids': ['anvato 899441'],
  192. },
  193. }, {
  194. 'url': 'https://www.chiefs.com/listen/patrick-mahomes-travis-kelce-react-to-win-over-dolphins-the-breakdown',
  195. 'md5': '92a517f05bd3eb50fe50244bc621aec8',
  196. 'info_dict': {
  197. 'id': '8b7c3625-a461-4751-8db4-85f536f2bbd0',
  198. 'ext': 'mp3',
  199. 'title': 'Patrick Mahomes, Travis Kelce React to Win Over Dolphins | The Breakdown',
  200. 'description': 'md5:12ada8ee70e6762658c30e223e095075',
  201. 'thumbnail': 'https://static.clubs.nfl.com/image/private/t_editorial_landscape_12_desktop/v1571153441/chiefs/rfljejccnyhhkpkfq855',
  202. },
  203. }, {
  204. 'url': 'https://www.buffalobills.com/video/buffalo-bills-military-recognition-week-14',
  205. 'only_matching': True,
  206. }, {
  207. 'url': 'https://www.raiders.com/audio/instant-reactions-raiders-week-14-loss-to-indianapolis-colts-espn-jason-fitz',
  208. 'only_matching': True,
  209. }]
  210. def _real_extract(self, url):
  211. display_id = self._match_id(url)
  212. webpage = self._download_webpage(url, display_id)
  213. return self._parse_video_config(self._search_regex(
  214. self._VIDEO_CONFIG_REGEX, webpage, 'video config'), display_id)
  215. class NFLArticleIE(NFLBaseIE):
  216. IE_NAME = 'nfl.com:article'
  217. _VALID_URL = NFLBaseIE._VALID_URL_BASE + r'news/(?P<id>[^/#?&]+)'
  218. _TEST = {
  219. 'url': 'https://www.buffalobills.com/news/the-only-thing-we-ve-earned-is-the-noise-bills-coaches-discuss-handling-rising-e',
  220. 'info_dict': {
  221. 'id': 'the-only-thing-we-ve-earned-is-the-noise-bills-coaches-discuss-handling-rising-e',
  222. 'title': "'The only thing we've earned is the noise' | Bills coaches discuss handling rising expectations",
  223. },
  224. 'playlist_count': 4,
  225. }
  226. def _real_extract(self, url):
  227. display_id = self._match_id(url)
  228. webpage = self._download_webpage(url, display_id)
  229. def entries():
  230. for video_config in re.findall(self._VIDEO_CONFIG_REGEX, webpage):
  231. yield self._parse_video_config(video_config, display_id)
  232. title = clean_html(get_element_by_class(
  233. 'nfl-c-article__title', webpage)) or self._html_search_meta(
  234. ['og:title', 'twitter:title'], webpage)
  235. return self.playlist_result(entries(), display_id, title)
  236. class NFLPlusReplayIE(NFLBaseIE):
  237. IE_NAME = 'nfl.com:plus:replay'
  238. _VALID_URL = r'https?://(?:www\.)?nfl\.com/plus/games/(?P<slug>[\w-]+)(?:/(?P<id>\d+))?'
  239. _TESTS = [{
  240. 'url': 'https://www.nfl.com/plus/games/giants-at-vikings-2022-post-1/1572108',
  241. 'info_dict': {
  242. 'id': '1572108',
  243. 'ext': 'mp4',
  244. 'title': 'New York Giants at Minnesota Vikings',
  245. 'description': 'New York Giants play the Minnesota Vikings at U.S. Bank Stadium on January 15, 2023',
  246. 'uploader': 'NFL',
  247. 'upload_date': '20230116',
  248. 'timestamp': 1673864520,
  249. 'duration': 7157,
  250. 'categories': ['Game Highlights'],
  251. 'tags': ['Minnesota Vikings', 'New York Giants', 'Minnesota Vikings vs. New York Giants'],
  252. 'thumbnail': r're:^https?://.*\.jpg',
  253. },
  254. 'params': {'skip_download': 'm3u8'},
  255. }, {
  256. 'note': 'Subscription required',
  257. 'url': 'https://www.nfl.com/plus/games/giants-at-vikings-2022-post-1',
  258. 'playlist_count': 4,
  259. 'info_dict': {
  260. 'id': 'giants-at-vikings-2022-post-1',
  261. },
  262. }, {
  263. 'note': 'Subscription required',
  264. 'url': 'https://www.nfl.com/plus/games/giants-at-patriots-2011-pre-4',
  265. 'playlist_count': 2,
  266. 'info_dict': {
  267. 'id': 'giants-at-patriots-2011-pre-4',
  268. },
  269. }, {
  270. 'note': 'Subscription required',
  271. 'url': 'https://www.nfl.com/plus/games/giants-at-patriots-2011-pre-4',
  272. 'info_dict': {
  273. 'id': '950701',
  274. 'ext': 'mp4',
  275. 'title': 'Giants @ Patriots',
  276. 'description': 'Giants at Patriots on September 01, 2011',
  277. 'uploader': 'NFL',
  278. 'upload_date': '20210724',
  279. 'timestamp': 1627085874,
  280. 'duration': 1532,
  281. 'categories': ['Game Highlights'],
  282. 'tags': ['play-by-play'],
  283. 'thumbnail': r're:^https?://.*\.jpg',
  284. },
  285. 'params': {
  286. 'skip_download': 'm3u8',
  287. 'extractor_args': {'nflplusreplay': {'type': ['condensed_game']}},
  288. },
  289. }]
  290. _REPLAY_TYPES = {
  291. 'full_game': 'Full Game',
  292. 'full_game_spanish': 'Full Game - Spanish',
  293. 'condensed_game': 'Condensed Game',
  294. 'all_22': 'All-22',
  295. }
  296. def _real_initialize(self):
  297. self._get_account_info()
  298. def _real_extract(self, url):
  299. slug, video_id = self._match_valid_url(url).group('slug', 'id')
  300. requested_types = self._configuration_arg('type', ['all'])
  301. if 'all' in requested_types:
  302. requested_types = list(self._REPLAY_TYPES.keys())
  303. requested_types = traverse_obj(self._REPLAY_TYPES, (None, requested_types))
  304. if not video_id:
  305. self._get_auth_token()
  306. headers = {'Authorization': f'Bearer {self._TOKEN}'}
  307. game_id = self._download_json(
  308. f'https://api.nfl.com/football/v2/games/externalId/slug/{slug}', slug,
  309. 'Downloading game ID', query={'withExternalIds': 'true'}, headers=headers)['id']
  310. replays = self._download_json(
  311. 'https://api.nfl.com/content/v1/videos/replays', slug, 'Downloading replays JSON',
  312. query={'gameId': game_id}, headers=headers)
  313. if len(requested_types) == 1:
  314. video_id = traverse_obj(replays, (
  315. 'items', lambda _, v: v['subType'] == requested_types[0], 'mcpPlaybackId'), get_all=False)
  316. if video_id:
  317. return self._extract_video(video_id)
  318. def entries():
  319. for replay in traverse_obj(
  320. replays, ('items', lambda _, v: v['mcpPlaybackId'] and v['subType'] in requested_types),
  321. ):
  322. yield self._extract_video(replay['mcpPlaybackId'])
  323. return self.playlist_result(entries(), slug)
  324. class NFLPlusEpisodeIE(NFLBaseIE):
  325. IE_NAME = 'nfl.com:plus:episode'
  326. _VALID_URL = r'https?://(?:www\.)?nfl\.com/plus/episodes/(?P<id>[\w-]+)'
  327. _TESTS = [{
  328. 'note': 'Subscription required',
  329. 'url': 'https://www.nfl.com/plus/episodes/kurt-s-qb-insider-conference-championships',
  330. 'info_dict': {
  331. 'id': '1576832',
  332. 'ext': 'mp4',
  333. 'title': 'Conference Championships',
  334. 'description': 'md5:944f7fab56f7a37430bf8473f5473857',
  335. 'uploader': 'NFL',
  336. 'upload_date': '20230127',
  337. 'timestamp': 1674782760,
  338. 'duration': 730,
  339. 'categories': ['Analysis'],
  340. 'tags': ['Cincinnati Bengals at Kansas City Chiefs (2022-POST-3)'],
  341. 'thumbnail': r're:^https?://.*\.jpg',
  342. },
  343. 'params': {'skip_download': 'm3u8'},
  344. }]
  345. def _real_initialize(self):
  346. self._get_account_info()
  347. def _real_extract(self, url):
  348. slug = self._match_id(url)
  349. self._get_auth_token()
  350. video_id = self._download_json(
  351. f'https://api.nfl.com/content/v1/videos/episodes/{slug}', slug, headers={
  352. 'Authorization': f'Bearer {self._TOKEN}',
  353. })['mcpPlaybackId']
  354. return self._extract_video(video_id)