mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-11-04 08:35:12 +00:00 
			
		
		
		
	[extractor/anvato] Fix extractor and refactor (#5074)
Authored by: bashonly
This commit is contained in:
		
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							@@ -74,8 +74,7 @@ offlinetest: codetest
 | 
				
			|||||||
	$(PYTHON) -m pytest -k "not download"
 | 
						$(PYTHON) -m pytest -k "not download"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# XXX: This is hard to maintain
 | 
					# XXX: This is hard to maintain
 | 
				
			||||||
CODE_FOLDERS = yt_dlp yt_dlp/downloader yt_dlp/extractor yt_dlp/postprocessor yt_dlp/compat \
 | 
					CODE_FOLDERS = yt_dlp yt_dlp/downloader yt_dlp/extractor yt_dlp/postprocessor yt_dlp/compat
 | 
				
			||||||
               yt_dlp/extractor/anvato_token_generator
 | 
					 | 
				
			||||||
yt-dlp: yt_dlp/*.py yt_dlp/*/*.py
 | 
					yt-dlp: yt_dlp/*.py yt_dlp/*/*.py
 | 
				
			||||||
	mkdir -p zip
 | 
						mkdir -p zip
 | 
				
			||||||
	for d in $(CODE_FOLDERS) ; do \
 | 
						for d in $(CODE_FOLDERS) ; do \
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							@@ -32,7 +32,6 @@ def packages():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return [
 | 
					    return [
 | 
				
			||||||
        'yt_dlp', 'yt_dlp.extractor', 'yt_dlp.downloader', 'yt_dlp.postprocessor', 'yt_dlp.compat',
 | 
					        'yt_dlp', 'yt_dlp.extractor', 'yt_dlp.downloader', 'yt_dlp.postprocessor', 'yt_dlp.compat',
 | 
				
			||||||
        'yt_dlp.extractor.anvato_token_generator',
 | 
					 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,8 @@ import random
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .anvato_token_generator import NFLTokenGenerator
 | 
					 | 
				
			||||||
from .common import InfoExtractor
 | 
					from .common import InfoExtractor
 | 
				
			||||||
from ..aes import aes_encrypt
 | 
					from ..aes import aes_encrypt
 | 
				
			||||||
from ..compat import compat_str
 | 
					 | 
				
			||||||
from ..utils import (
 | 
					from ..utils import (
 | 
				
			||||||
    bytes_to_intlist,
 | 
					    bytes_to_intlist,
 | 
				
			||||||
    determine_ext,
 | 
					    determine_ext,
 | 
				
			||||||
@@ -16,20 +14,61 @@ from ..utils import (
 | 
				
			|||||||
    int_or_none,
 | 
					    int_or_none,
 | 
				
			||||||
    join_nonempty,
 | 
					    join_nonempty,
 | 
				
			||||||
    strip_jsonp,
 | 
					    strip_jsonp,
 | 
				
			||||||
 | 
					    smuggle_url,
 | 
				
			||||||
 | 
					    traverse_obj,
 | 
				
			||||||
    unescapeHTML,
 | 
					    unescapeHTML,
 | 
				
			||||||
    unsmuggle_url,
 | 
					    unsmuggle_url,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def md5_text(s):
 | 
					def md5_text(s):
 | 
				
			||||||
    if not isinstance(s, compat_str):
 | 
					    return hashlib.md5(str(s).encode()).hexdigest()
 | 
				
			||||||
        s = compat_str(s)
 | 
					 | 
				
			||||||
    return hashlib.md5(s.encode('utf-8')).hexdigest()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AnvatoIE(InfoExtractor):
 | 
					class AnvatoIE(InfoExtractor):
 | 
				
			||||||
    _VALID_URL = r'anvato:(?P<access_key_or_mcp>[^:]+):(?P<id>\d+)'
 | 
					    _VALID_URL = r'anvato:(?P<access_key_or_mcp>[^:]+):(?P<id>\d+)'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _API_BASE_URL = 'https://tkx.mp.lura.live/rest/v2'
 | 
				
			||||||
 | 
					    _ANVP_RE = r'<script[^>]+\bdata-anvp\s*=\s*(["\'])(?P<anvp>(?:(?!\1).)+)\1'
 | 
				
			||||||
 | 
					    _AUTH_KEY = b'\x31\xc2\x42\x84\x9e\x73\xa0\xce'  # from anvplayer.min.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _TESTS = [{
 | 
				
			||||||
 | 
					        # from https://www.nfl.com/videos/baker-mayfield-s-game-changing-plays-from-3-td-game-week-14
 | 
				
			||||||
 | 
					        'url': 'anvato:GXvEgwyJeWem8KCYXfeoHWknwP48Mboj:899441',
 | 
				
			||||||
 | 
					        'md5': '921919dab3cd0b849ff3d624831ae3e2',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '899441',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': 'Baker Mayfield\'s game-changing plays from 3-TD game Week 14',
 | 
				
			||||||
 | 
					            'description': 'md5:85e05a3cc163f8c344340f220521136d',
 | 
				
			||||||
 | 
					            'upload_date': '20201215',
 | 
				
			||||||
 | 
					            'timestamp': 1608009755,
 | 
				
			||||||
 | 
					            'thumbnail': r're:^https?://.*\.jpg',
 | 
				
			||||||
 | 
					            'uploader': 'NFL',
 | 
				
			||||||
 | 
					            'tags': ['Baltimore Ravens at Cleveland Browns (2020-REG-14)', 'Baker Mayfield', 'Game Highlights',
 | 
				
			||||||
 | 
					                     'Player Highlights', 'Cleveland Browns', 'league'],
 | 
				
			||||||
 | 
					            'duration': 157,
 | 
				
			||||||
 | 
					            'categories': ['Entertainment', 'Game', 'Highlights'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }, {
 | 
				
			||||||
 | 
					        # from https://ktla.com/news/99-year-old-woman-learns-to-fly-in-torrance-checks-off-bucket-list-dream/
 | 
				
			||||||
 | 
					        'url': 'anvato:X8POa4zpGZMmeiq0wqiO8IP5rMqQM9VN:8032455',
 | 
				
			||||||
 | 
					        'md5': '837718bcfb3a7778d022f857f7a9b19e',
 | 
				
			||||||
 | 
					        'info_dict': {
 | 
				
			||||||
 | 
					            'id': '8032455',
 | 
				
			||||||
 | 
					            'ext': 'mp4',
 | 
				
			||||||
 | 
					            'title': '99-year-old woman learns to fly plane in Torrance, checks off bucket list dream',
 | 
				
			||||||
 | 
					            'description': 'md5:0a12bab8159445e78f52a297a35c6609',
 | 
				
			||||||
 | 
					            'upload_date': '20220928',
 | 
				
			||||||
 | 
					            'timestamp': 1664408881,
 | 
				
			||||||
 | 
					            'thumbnail': r're:^https?://.*\.jpg',
 | 
				
			||||||
 | 
					            'uploader': 'LIN',
 | 
				
			||||||
 | 
					            'tags': ['video', 'news', '5live'],
 | 
				
			||||||
 | 
					            'duration': 155,
 | 
				
			||||||
 | 
					            'categories': ['News'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Copied from anvplayer.min.js
 | 
					    # Copied from anvplayer.min.js
 | 
				
			||||||
    _ANVACK_TABLE = {
 | 
					    _ANVACK_TABLE = {
 | 
				
			||||||
        'nbcu_nbcd_desktop_web_prod_93d8ead38ce2024f8f544b78306fbd15895ae5e6': 'NNemUkySjxLyPTKvZRiGntBIjEyK8uqicjMakIaQ',
 | 
					        'nbcu_nbcd_desktop_web_prod_93d8ead38ce2024f8f544b78306fbd15895ae5e6': 'NNemUkySjxLyPTKvZRiGntBIjEyK8uqicjMakIaQ',
 | 
				
			||||||
@@ -202,86 +241,74 @@ class AnvatoIE(InfoExtractor):
 | 
				
			|||||||
        'telemundo': 'anvato_mcp_telemundo_web_prod_c5278d51ad46fda4b6ca3d0ea44a7846a054f582'
 | 
					        'telemundo': 'anvato_mcp_telemundo_web_prod_c5278d51ad46fda4b6ca3d0ea44a7846a054f582'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _generate_nfl_token(self, anvack, mcp_id):
 | 
				
			||||||
 | 
					        reroute = self._download_json(
 | 
				
			||||||
 | 
					            'https://api.nfl.com/v1/reroute', mcp_id, data=b'grant_type=client_credentials',
 | 
				
			||||||
 | 
					            headers={'X-Domain-Id': 100}, note='Fetching token info')
 | 
				
			||||||
 | 
					        token_type = reroute.get('token_type') or 'Bearer'
 | 
				
			||||||
 | 
					        auth_token = f'{token_type} {reroute["access_token"]}'
 | 
				
			||||||
 | 
					        response = self._download_json(
 | 
				
			||||||
 | 
					            'https://api.nfl.com/v3/shield/', mcp_id, data=json.dumps({
 | 
				
			||||||
 | 
					                'query': '''{
 | 
				
			||||||
 | 
					  viewer {
 | 
				
			||||||
 | 
					    mediaToken(anvack: "%s", id: %s) {
 | 
				
			||||||
 | 
					      token
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}''' % (anvack, mcp_id),
 | 
				
			||||||
 | 
					            }).encode(), headers={
 | 
				
			||||||
 | 
					                'Authorization': auth_token,
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					            }, note='Fetching NFL API token')
 | 
				
			||||||
 | 
					        return traverse_obj(response, ('data', 'viewer', 'mediaToken', 'token'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TOKEN_GENERATORS = {
 | 
					    _TOKEN_GENERATORS = {
 | 
				
			||||||
        'GXvEgwyJeWem8KCYXfeoHWknwP48Mboj': NFLTokenGenerator,
 | 
					        'GXvEgwyJeWem8KCYXfeoHWknwP48Mboj': _generate_nfl_token,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _API_KEY = '3hwbSuqqT690uxjNYBktSQpa5ZrpYYR0Iofx7NcJHyA'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _ANVP_RE = r'<script[^>]+\bdata-anvp\s*=\s*(["\'])(?P<anvp>(?:(?!\1).)+)\1'
 | 
					 | 
				
			||||||
    _AUTH_KEY = b'\x31\xc2\x42\x84\x9e\x73\xa0\xce'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _TESTS = [{
 | 
					 | 
				
			||||||
        # from https://www.boston25news.com/news/watch-humpback-whale-breaches-right-next-to-fishing-boat-near-nh/817484874
 | 
					 | 
				
			||||||
        'url': 'anvato:8v9BEynrwx8EFLYpgfOWcG1qJqyXKlRM:4465496',
 | 
					 | 
				
			||||||
        'info_dict': {
 | 
					 | 
				
			||||||
            'id': '4465496',
 | 
					 | 
				
			||||||
            'ext': 'mp4',
 | 
					 | 
				
			||||||
            'title': 'VIDEO: Humpback whale breaches right next to NH boat',
 | 
					 | 
				
			||||||
            'description': 'VIDEO: Humpback whale breaches right next to NH boat. Footage courtesy: Zach Fahey.',
 | 
					 | 
				
			||||||
            'duration': 22,
 | 
					 | 
				
			||||||
            'timestamp': 1534855680,
 | 
					 | 
				
			||||||
            'upload_date': '20180821',
 | 
					 | 
				
			||||||
            'uploader': 'ANV',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        'params': {
 | 
					 | 
				
			||||||
            'skip_download': True,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
        # from https://sanfrancisco.cbslocal.com/2016/06/17/source-oakland-cop-on-leave-for-having-girlfriend-help-with-police-reports/
 | 
					 | 
				
			||||||
        'url': 'anvato:DVzl9QRzox3ZZsP9bNu5Li3X7obQOnqP:3417601',
 | 
					 | 
				
			||||||
        'only_matching': True,
 | 
					 | 
				
			||||||
    }]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        super(AnvatoIE, self).__init__(*args, **kwargs)
 | 
					 | 
				
			||||||
        self.__server_time = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _server_time(self, access_key, video_id):
 | 
					    def _server_time(self, access_key, video_id):
 | 
				
			||||||
        if self.__server_time is not None:
 | 
					        return int_or_none(traverse_obj(self._download_json(
 | 
				
			||||||
            return self.__server_time
 | 
					            f'{self._API_BASE_URL}/server_time', video_id, query={'anvack': access_key},
 | 
				
			||||||
 | 
					            note='Fetching server time', fatal=False), 'server_time')) or int(time.time())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.__server_time = int(self._download_json(
 | 
					    def _get_video_json(self, access_key, video_id, extracted_token):
 | 
				
			||||||
            self._api_prefix(access_key) + 'server_time?anvack=' + access_key, video_id,
 | 
					 | 
				
			||||||
            note='Fetching server time')['server_time'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self.__server_time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _api_prefix(self, access_key):
 | 
					 | 
				
			||||||
        return 'https://tkx2-%s.anvato.net/rest/v2/' % ('prod' if 'prod' in access_key else 'stage')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_video_json(self, access_key, video_id):
 | 
					 | 
				
			||||||
        # See et() in anvplayer.min.js, which is an alias of getVideoJSON()
 | 
					        # See et() in anvplayer.min.js, which is an alias of getVideoJSON()
 | 
				
			||||||
        video_data_url = self._api_prefix(access_key) + 'mcp/video/%s?anvack=%s' % (video_id, access_key)
 | 
					        video_data_url = f'{self._API_BASE_URL}/mcp/video/{video_id}?anvack={access_key}'
 | 
				
			||||||
        server_time = self._server_time(access_key, video_id)
 | 
					        server_time = self._server_time(access_key, video_id)
 | 
				
			||||||
        input_data = '%d~%s~%s' % (server_time, md5_text(video_data_url), md5_text(server_time))
 | 
					        input_data = f'{server_time}~{md5_text(video_data_url)}~{md5_text(server_time)}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        auth_secret = intlist_to_bytes(aes_encrypt(
 | 
					        auth_secret = intlist_to_bytes(aes_encrypt(
 | 
				
			||||||
            bytes_to_intlist(input_data[:64]), bytes_to_intlist(self._AUTH_KEY)))
 | 
					            bytes_to_intlist(input_data[:64]), bytes_to_intlist(self._AUTH_KEY)))
 | 
				
			||||||
 | 
					        query = {
 | 
				
			||||||
        video_data_url += '&X-Anvato-Adst-Auth=' + base64.b64encode(auth_secret).decode('ascii')
 | 
					            'X-Anvato-Adst-Auth': base64.b64encode(auth_secret).decode('ascii'),
 | 
				
			||||||
 | 
					            'rtyp': 'fp',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        anvrid = md5_text(time.time() * 1000 * random.random())[:30]
 | 
					        anvrid = md5_text(time.time() * 1000 * random.random())[:30]
 | 
				
			||||||
        api = {
 | 
					        api = {
 | 
				
			||||||
            'anvrid': anvrid,
 | 
					            'anvrid': anvrid,
 | 
				
			||||||
            'anvts': server_time,
 | 
					            'anvts': server_time,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if self._TOKEN_GENERATORS.get(access_key) is not None:
 | 
					        if extracted_token is not None:
 | 
				
			||||||
            api['anvstk2'] = self._TOKEN_GENERATORS[access_key].generate(self, access_key, video_id)
 | 
					            api['anvstk2'] = extracted_token
 | 
				
			||||||
 | 
					        elif self._TOKEN_GENERATORS.get(access_key) is not None:
 | 
				
			||||||
 | 
					            api['anvstk2'] = self._TOKEN_GENERATORS[access_key](self, access_key, video_id)
 | 
				
			||||||
 | 
					        elif self._ANVACK_TABLE.get(access_key) is not None:
 | 
				
			||||||
 | 
					            api['anvstk'] = md5_text(f'{access_key}|{anvrid}|{server_time}|{self._ANVACK_TABLE[access_key]}')
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            api['anvstk'] = md5_text('%s|%s|%d|%s' % (
 | 
					            api['anvstk2'] = 'default'
 | 
				
			||||||
                access_key, anvrid, server_time,
 | 
					 | 
				
			||||||
                self._ANVACK_TABLE.get(access_key, self._API_KEY)))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self._download_json(
 | 
					        return self._download_json(
 | 
				
			||||||
            video_data_url, video_id, transform_source=strip_jsonp,
 | 
					            video_data_url, video_id, transform_source=strip_jsonp, query=query,
 | 
				
			||||||
            data=json.dumps({'api': api}).encode('utf-8'))
 | 
					            data=json.dumps({'api': api}, separators=(',', ':')).encode('utf-8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_anvato_videos(self, access_key, video_id):
 | 
					    def _get_anvato_videos(self, access_key, video_id, token):
 | 
				
			||||||
        video_data = self._get_video_json(access_key, video_id)
 | 
					        video_data = self._get_video_json(access_key, video_id, token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        formats = []
 | 
					        formats = []
 | 
				
			||||||
        for published_url in video_data['published_urls']:
 | 
					        for published_url in video_data['published_urls']:
 | 
				
			||||||
            video_url = published_url['embed_url']
 | 
					            video_url = published_url.get('embed_url')
 | 
				
			||||||
 | 
					            if not video_url:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
            media_format = published_url.get('format')
 | 
					            media_format = published_url.get('format')
 | 
				
			||||||
            ext = determine_ext(video_url)
 | 
					            ext = determine_ext(video_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -296,15 +323,27 @@ class AnvatoIE(InfoExtractor):
 | 
				
			|||||||
                'tbr': tbr or None,
 | 
					                'tbr': tbr or None,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if media_format == 'm3u8' and tbr is not None:
 | 
					            vtt_subs, hls_subs = {}, {}
 | 
				
			||||||
 | 
					            if media_format == 'vtt':
 | 
				
			||||||
 | 
					                _, vtt_subs = self._extract_m3u8_formats_and_subtitles(
 | 
				
			||||||
 | 
					                    video_url, video_id, m3u8_id='vtt', fatal=False)
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            elif media_format == 'm3u8' and tbr is not None:
 | 
				
			||||||
                a_format.update({
 | 
					                a_format.update({
 | 
				
			||||||
                    'format_id': join_nonempty('hls', tbr),
 | 
					                    'format_id': join_nonempty('hls', tbr),
 | 
				
			||||||
                    'ext': 'mp4',
 | 
					                    'ext': 'mp4',
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            elif media_format == 'm3u8-variant' or ext == 'm3u8':
 | 
					            elif media_format == 'm3u8-variant' or ext == 'm3u8':
 | 
				
			||||||
                formats.extend(self._extract_m3u8_formats(
 | 
					                # For some videos the initial m3u8 URL returns JSON instead
 | 
				
			||||||
                    video_url, video_id, 'mp4', entry_protocol='m3u8_native',
 | 
					                manifest_json = self._download_json(
 | 
				
			||||||
                    m3u8_id='hls', fatal=False))
 | 
					                    video_url, video_id, note='Downloading manifest JSON', errnote=False)
 | 
				
			||||||
 | 
					                if manifest_json:
 | 
				
			||||||
 | 
					                    video_url = manifest_json.get('master_m3u8')
 | 
				
			||||||
 | 
					                    if not video_url:
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					                hls_fmts, hls_subs = self._extract_m3u8_formats_and_subtitles(
 | 
				
			||||||
 | 
					                    video_url, video_id, ext='mp4', m3u8_id='hls', fatal=False)
 | 
				
			||||||
 | 
					                formats.extend(hls_fmts)
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            elif ext == 'mp3' or media_format == 'mp3':
 | 
					            elif ext == 'mp3' or media_format == 'mp3':
 | 
				
			||||||
                a_format['vcodec'] = 'none'
 | 
					                a_format['vcodec'] = 'none'
 | 
				
			||||||
@@ -324,6 +363,7 @@ class AnvatoIE(InfoExtractor):
 | 
				
			|||||||
                'ext': 'tt' if caption.get('format') == 'SMPTE-TT' else None
 | 
					                'ext': 'tt' if caption.get('format') == 'SMPTE-TT' else None
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            subtitles.setdefault(caption['language'], []).append(a_caption)
 | 
					            subtitles.setdefault(caption['language'], []).append(a_caption)
 | 
				
			||||||
 | 
					        subtitles = self._merge_subtitles(subtitles, hls_subs, vtt_subs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'id': video_id,
 | 
					            'id': video_id,
 | 
				
			||||||
@@ -349,7 +389,10 @@ class AnvatoIE(InfoExtractor):
 | 
				
			|||||||
                access_key = cls._MCP_TO_ACCESS_KEY_TABLE.get((anvplayer_data.get('mcp') or '').lower())
 | 
					                access_key = cls._MCP_TO_ACCESS_KEY_TABLE.get((anvplayer_data.get('mcp') or '').lower())
 | 
				
			||||||
            if not (video_id or '').isdigit() or not access_key:
 | 
					            if not (video_id or '').isdigit() or not access_key:
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            yield cls.url_result(f'anvato:{access_key}:{video_id}', AnvatoIE, video_id)
 | 
					            url = f'anvato:{access_key}:{video_id}'
 | 
				
			||||||
 | 
					            if anvplayer_data.get('token'):
 | 
				
			||||||
 | 
					                url = smuggle_url(url, {'token': anvplayer_data['token']})
 | 
				
			||||||
 | 
					            yield cls.url_result(url, AnvatoIE, video_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _extract_anvato_videos(self, webpage, video_id):
 | 
					    def _extract_anvato_videos(self, webpage, video_id):
 | 
				
			||||||
        anvplayer_data = self._parse_json(
 | 
					        anvplayer_data = self._parse_json(
 | 
				
			||||||
@@ -357,7 +400,7 @@ class AnvatoIE(InfoExtractor):
 | 
				
			|||||||
                self._ANVP_RE, webpage, 'Anvato player data', group='anvp'),
 | 
					                self._ANVP_RE, webpage, 'Anvato player data', group='anvp'),
 | 
				
			||||||
            video_id)
 | 
					            video_id)
 | 
				
			||||||
        return self._get_anvato_videos(
 | 
					        return self._get_anvato_videos(
 | 
				
			||||||
            anvplayer_data['accessKey'], anvplayer_data['video'])
 | 
					            anvplayer_data['accessKey'], anvplayer_data['video'], 'default')  # cbslocal token = 'default'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _real_extract(self, url):
 | 
					    def _real_extract(self, url):
 | 
				
			||||||
        url, smuggled_data = unsmuggle_url(url, {})
 | 
					        url, smuggled_data = unsmuggle_url(url, {})
 | 
				
			||||||
@@ -365,9 +408,7 @@ class AnvatoIE(InfoExtractor):
 | 
				
			|||||||
            'countries': smuggled_data.get('geo_countries'),
 | 
					            'countries': smuggled_data.get('geo_countries'),
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mobj = self._match_valid_url(url)
 | 
					        access_key, video_id = self._match_valid_url(url).group('access_key_or_mcp', 'id')
 | 
				
			||||||
        access_key, video_id = mobj.group('access_key_or_mcp', 'id')
 | 
					 | 
				
			||||||
        if access_key not in self._ANVACK_TABLE:
 | 
					        if access_key not in self._ANVACK_TABLE:
 | 
				
			||||||
            access_key = self._MCP_TO_ACCESS_KEY_TABLE.get(
 | 
					            access_key = self._MCP_TO_ACCESS_KEY_TABLE.get(access_key) or access_key
 | 
				
			||||||
                access_key) or access_key
 | 
					        return self._get_anvato_videos(access_key, video_id, smuggled_data.get('token'))
 | 
				
			||||||
        return self._get_anvato_videos(access_key, video_id)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
from .nfl import NFLTokenGenerator
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
__all__ = [
 | 
					 | 
				
			||||||
    'NFLTokenGenerator',
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
class TokenGenerator:
 | 
					 | 
				
			||||||
    def generate(self, anvack, mcp_id):
 | 
					 | 
				
			||||||
        raise NotImplementedError('This method must be implemented by subclasses')
 | 
					 | 
				
			||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
import json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .common import TokenGenerator
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class NFLTokenGenerator(TokenGenerator):
 | 
					 | 
				
			||||||
    _AUTHORIZATION = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def generate(ie, anvack, mcp_id):
 | 
					 | 
				
			||||||
        if not NFLTokenGenerator._AUTHORIZATION:
 | 
					 | 
				
			||||||
            reroute = ie._download_json(
 | 
					 | 
				
			||||||
                'https://api.nfl.com/v1/reroute', mcp_id,
 | 
					 | 
				
			||||||
                data=b'grant_type=client_credentials',
 | 
					 | 
				
			||||||
                headers={'X-Domain-Id': 100})
 | 
					 | 
				
			||||||
            NFLTokenGenerator._AUTHORIZATION = '%s %s' % (reroute.get('token_type') or 'Bearer', reroute['access_token'])
 | 
					 | 
				
			||||||
        return ie._download_json(
 | 
					 | 
				
			||||||
            'https://api.nfl.com/v3/shield/', mcp_id, data=json.dumps({
 | 
					 | 
				
			||||||
                'query': '''{
 | 
					 | 
				
			||||||
  viewer {
 | 
					 | 
				
			||||||
    mediaToken(anvack: "%s", id: %s) {
 | 
					 | 
				
			||||||
      token
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}''' % (anvack, mcp_id),
 | 
					 | 
				
			||||||
            }).encode(), headers={
 | 
					 | 
				
			||||||
                'Authorization': NFLTokenGenerator._AUTHORIZATION,
 | 
					 | 
				
			||||||
                'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
            })['data']['viewer']['mediaToken']['token']
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user