mirror of
				https://github.com/ytdl-org/youtube-dl.git
				synced 2025-11-01 09:26:45 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			429 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			429 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from __future__ import unicode_literals
 | 
						|
 | 
						|
import functools
 | 
						|
import re
 | 
						|
 | 
						|
from .turner import TurnerBaseIE
 | 
						|
from ..compat import (
 | 
						|
    compat_parse_qs,
 | 
						|
    compat_str,
 | 
						|
    compat_urllib_parse_unquote,
 | 
						|
    compat_urllib_parse_urlparse,
 | 
						|
)
 | 
						|
from ..utils import (
 | 
						|
    int_or_none,
 | 
						|
    merge_dicts,
 | 
						|
    OnDemandPagedList,
 | 
						|
    parse_duration,
 | 
						|
    parse_iso8601,
 | 
						|
    try_get,
 | 
						|
    update_url_query,
 | 
						|
    urljoin,
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
class NBACVPBaseIE(TurnerBaseIE):
 | 
						|
    def _extract_nba_cvp_info(self, path, video_id, fatal=False):
 | 
						|
        return self._extract_cvp_info(
 | 
						|
            'http://secure.nba.com/%s' % path, video_id, {
 | 
						|
                'default': {
 | 
						|
                    'media_src': 'http://nba.cdn.turner.com/nba/big',
 | 
						|
                },
 | 
						|
                'm3u8': {
 | 
						|
                    'media_src': 'http://nbavod-f.akamaihd.net',
 | 
						|
                },
 | 
						|
            }, fatal=fatal)
 | 
						|
 | 
						|
 | 
						|
class NBAWatchBaseIE(NBACVPBaseIE):
 | 
						|
    _VALID_URL_BASE = r'https?://(?:(?:www\.)?nba\.com(?:/watch)?|watch\.nba\.com)/'
 | 
						|
 | 
						|
    def _extract_video(self, filter_key, filter_value):
 | 
						|
        video = self._download_json(
 | 
						|
            'https://neulionscnbav2-a.akamaihd.net/solr/nbad_program/usersearch',
 | 
						|
            filter_value, query={
 | 
						|
                'fl': 'description,image,name,pid,releaseDate,runtime,tags,seoName',
 | 
						|
                'q': filter_key + ':' + filter_value,
 | 
						|
                'wt': 'json',
 | 
						|
            })['response']['docs'][0]
 | 
						|
 | 
						|
        video_id = str(video['pid'])
 | 
						|
        title = video['name']
 | 
						|
 | 
						|
        formats = []
 | 
						|
        m3u8_url = (self._download_json(
 | 
						|
            'https://watch.nba.com/service/publishpoint', video_id, query={
 | 
						|
                'type': 'video',
 | 
						|
                'format': 'json',
 | 
						|
                'id': video_id,
 | 
						|
            }, headers={
 | 
						|
                'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0_1 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A402 Safari/604.1',
 | 
						|
            }, fatal=False) or {}).get('path')
 | 
						|
        if m3u8_url:
 | 
						|
            m3u8_formats = self._extract_m3u8_formats(
 | 
						|
                re.sub(r'_(?:pc|iphone)\.', '.', m3u8_url), video_id, 'mp4',
 | 
						|
                'm3u8_native', m3u8_id='hls', fatal=False)
 | 
						|
            formats.extend(m3u8_formats)
 | 
						|
            for f in m3u8_formats:
 | 
						|
                http_f = f.copy()
 | 
						|
                http_f.update({
 | 
						|
                    'format_id': http_f['format_id'].replace('hls-', 'http-'),
 | 
						|
                    'protocol': 'http',
 | 
						|
                    'url': http_f['url'].replace('.m3u8', ''),
 | 
						|
                })
 | 
						|
                formats.append(http_f)
 | 
						|
 | 
						|
        info = {
 | 
						|
            'id': video_id,
 | 
						|
            'title': title,
 | 
						|
            'thumbnail': urljoin('https://nbadsdmt.akamaized.net/media/nba/nba/thumbs/', video.get('image')),
 | 
						|
            'description': video.get('description'),
 | 
						|
            'duration': int_or_none(video.get('runtime')),
 | 
						|
            'timestamp': parse_iso8601(video.get('releaseDate')),
 | 
						|
            'tags': video.get('tags'),
 | 
						|
        }
 | 
						|
 | 
						|
        seo_name = video.get('seoName')
 | 
						|
        if seo_name and re.search(r'\d{4}/\d{2}/\d{2}/', seo_name):
 | 
						|
            base_path = ''
 | 
						|
            if seo_name.startswith('teams/'):
 | 
						|
                base_path += seo_name.split('/')[1] + '/'
 | 
						|
            base_path += 'video/'
 | 
						|
            cvp_info = self._extract_nba_cvp_info(
 | 
						|
                base_path + seo_name + '.xml', video_id, False)
 | 
						|
            if cvp_info:
 | 
						|
                formats.extend(cvp_info['formats'])
 | 
						|
                info = merge_dicts(info, cvp_info)
 | 
						|
 | 
						|
        self._sort_formats(formats)
 | 
						|
        info['formats'] = formats
 | 
						|
        return info
 | 
						|
 | 
						|
 | 
						|
class NBAWatchEmbedIE(NBAWatchBaseIE):
 | 
						|
    IENAME = 'nba:watch:embed'
 | 
						|
    _VALID_URL = NBAWatchBaseIE._VALID_URL_BASE + r'embed\?.*?\bid=(?P<id>\d+)'
 | 
						|
    _TESTS = [{
 | 
						|
        'url': 'http://watch.nba.com/embed?id=659395',
 | 
						|
        'md5': 'b7e3f9946595f4ca0a13903ce5edd120',
 | 
						|
        'info_dict': {
 | 
						|
            'id': '659395',
 | 
						|
            'ext': 'mp4',
 | 
						|
            'title': 'Mix clip: More than 7 points of  Joe Ingles, Luc Mbah a Moute, Blake Griffin and 6 more in Utah Jazz vs. the Clippers, 4/15/2017',
 | 
						|
            'description': 'Mix clip: More than 7 points of  Joe Ingles, Luc Mbah a Moute, Blake Griffin and 6 more in Utah Jazz vs. the Clippers, 4/15/2017',
 | 
						|
            'timestamp': 1492228800,
 | 
						|
            'upload_date': '20170415',
 | 
						|
        },
 | 
						|
    }]
 | 
						|
 | 
						|
    def _real_extract(self, url):
 | 
						|
        video_id = self._match_id(url)
 | 
						|
        return self._extract_video('pid', video_id)
 | 
						|
 | 
						|
 | 
						|
class NBAWatchIE(NBAWatchBaseIE):
 | 
						|
    IE_NAME = 'nba:watch'
 | 
						|
    _VALID_URL = NBAWatchBaseIE._VALID_URL_BASE + r'(?:nba/)?video/(?P<id>.+?(?=/index\.html)|(?:[^/]+/)*[^/?#&]+)'
 | 
						|
    _TESTS = [{
 | 
						|
        'url': 'http://www.nba.com/video/games/nets/2012/12/04/0021200253-okc-bkn-recap.nba/index.html',
 | 
						|
        'md5': '9d902940d2a127af3f7f9d2f3dc79c96',
 | 
						|
        'info_dict': {
 | 
						|
            'id': '70946',
 | 
						|
            'ext': 'mp4',
 | 
						|
            'title': 'Thunder vs. Nets',
 | 
						|
            'description': 'Kevin Durant scores 32 points and dishes out six assists as the Thunder beat the Nets in Brooklyn.',
 | 
						|
            'duration': 181,
 | 
						|
            'timestamp': 1354597200,
 | 
						|
            'upload_date': '20121204',
 | 
						|
        },
 | 
						|
    }, {
 | 
						|
        'url': 'http://www.nba.com/video/games/hornets/2014/12/05/0021400276-nyk-cha-play5.nba/',
 | 
						|
        'only_matching': True,
 | 
						|
    }, {
 | 
						|
        'url': 'http://watch.nba.com/video/channels/playoffs/2015/05/20/0041400301-cle-atl-recap.nba',
 | 
						|
        'md5': 'b2b39b81cf28615ae0c3360a3f9668c4',
 | 
						|
        'info_dict': {
 | 
						|
            'id': '330865',
 | 
						|
            'ext': 'mp4',
 | 
						|
            'title': 'Hawks vs. Cavaliers Game 1',
 | 
						|
            'description': 'md5:8094c3498d35a9bd6b1a8c396a071b4d',
 | 
						|
            'duration': 228,
 | 
						|
            'timestamp': 1432094400,
 | 
						|
            'upload_date': '20150521',
 | 
						|
        },
 | 
						|
    }, {
 | 
						|
        'url': 'http://watch.nba.com/nba/video/channels/nba_tv/2015/06/11/YT_go_big_go_home_Game4_061115',
 | 
						|
        'only_matching': True,
 | 
						|
    }, {
 | 
						|
        # only CVP mp4 format available
 | 
						|
        'url': 'https://watch.nba.com/video/teams/cavaliers/2012/10/15/sloan121015mov-2249106',
 | 
						|
        'only_matching': True,
 | 
						|
    }, {
 | 
						|
        'url': 'https://watch.nba.com/video/top-100-dunks-from-the-2019-20-season?plsrc=nba&collection=2019-20-season-highlights',
 | 
						|
        'only_matching': True,
 | 
						|
    }]
 | 
						|
 | 
						|
    def _real_extract(self, url):
 | 
						|
        display_id = self._match_id(url)
 | 
						|
        collection_id = compat_parse_qs(compat_urllib_parse_urlparse(url).query).get('collection', [None])[0]
 | 
						|
        if collection_id:
 | 
						|
            if self._downloader.params.get('noplaylist'):
 | 
						|
                self.to_screen('Downloading just video %s because of --no-playlist' % display_id)
 | 
						|
            else:
 | 
						|
                self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % collection_id)
 | 
						|
                return self.url_result(
 | 
						|
                    'https://www.nba.com/watch/list/collection/' + collection_id,
 | 
						|
                    NBAWatchCollectionIE.ie_key(), collection_id)
 | 
						|
        return self._extract_video('seoName', display_id)
 | 
						|
 | 
						|
 | 
						|
class NBAWatchCollectionIE(NBAWatchBaseIE):
 | 
						|
    IE_NAME = 'nba:watch:collection'
 | 
						|
    _VALID_URL = NBAWatchBaseIE._VALID_URL_BASE + r'list/collection/(?P<id>[^/?#&]+)'
 | 
						|
    _TESTS = [{
 | 
						|
        'url': 'https://watch.nba.com/list/collection/season-preview-2020',
 | 
						|
        'info_dict': {
 | 
						|
            'id': 'season-preview-2020',
 | 
						|
        },
 | 
						|
        'playlist_mincount': 43,
 | 
						|
    }]
 | 
						|
    _PAGE_SIZE = 100
 | 
						|
 | 
						|
    def _fetch_page(self, collection_id, page):
 | 
						|
        page += 1
 | 
						|
        videos = self._download_json(
 | 
						|
            'https://content-api-prod.nba.com/public/1/endeavor/video-list/collection/' + collection_id,
 | 
						|
            collection_id, 'Downloading page %d JSON metadata' % page, query={
 | 
						|
                'count': self._PAGE_SIZE,
 | 
						|
                'page': page,
 | 
						|
            })['results']['videos']
 | 
						|
        for video in videos:
 | 
						|
            program = video.get('program') or {}
 | 
						|
            seo_name = program.get('seoName') or program.get('slug')
 | 
						|
            if not seo_name:
 | 
						|
                continue
 | 
						|
            yield {
 | 
						|
                '_type': 'url',
 | 
						|
                'id': program.get('id'),
 | 
						|
                'title': program.get('title') or video.get('title'),
 | 
						|
                'url': 'https://www.nba.com/watch/video/' + seo_name,
 | 
						|
                'thumbnail': video.get('image'),
 | 
						|
                'description': program.get('description') or video.get('description'),
 | 
						|
                'duration': parse_duration(program.get('runtimeHours')),
 | 
						|
                'timestamp': parse_iso8601(video.get('releaseDate')),
 | 
						|
            }
 | 
						|
 | 
						|
    def _real_extract(self, url):
 | 
						|
        collection_id = self._match_id(url)
 | 
						|
        entries = OnDemandPagedList(
 | 
						|
            functools.partial(self._fetch_page, collection_id),
 | 
						|
            self._PAGE_SIZE)
 | 
						|
        return self.playlist_result(entries, collection_id)
 | 
						|
 | 
						|
 | 
						|
class NBABaseIE(NBACVPBaseIE):
 | 
						|
    _VALID_URL_BASE = r'''(?x)
 | 
						|
        https?://(?:www\.)?nba\.com/
 | 
						|
            (?P<team>
 | 
						|
                blazers|
 | 
						|
                bucks|
 | 
						|
                bulls|
 | 
						|
                cavaliers|
 | 
						|
                celtics|
 | 
						|
                clippers|
 | 
						|
                grizzlies|
 | 
						|
                hawks|
 | 
						|
                heat|
 | 
						|
                hornets|
 | 
						|
                jazz|
 | 
						|
                kings|
 | 
						|
                knicks|
 | 
						|
                lakers|
 | 
						|
                magic|
 | 
						|
                mavericks|
 | 
						|
                nets|
 | 
						|
                nuggets|
 | 
						|
                pacers|
 | 
						|
                pelicans|
 | 
						|
                pistons|
 | 
						|
                raptors|
 | 
						|
                rockets|
 | 
						|
                sixers|
 | 
						|
                spurs|
 | 
						|
                suns|
 | 
						|
                thunder|
 | 
						|
                timberwolves|
 | 
						|
                warriors|
 | 
						|
                wizards
 | 
						|
            )
 | 
						|
        (?:/play\#)?/'''
 | 
						|
    _CHANNEL_PATH_REGEX = r'video/channel|series'
 | 
						|
 | 
						|
    def _embed_url_result(self, team, content_id):
 | 
						|
        return self.url_result(update_url_query(
 | 
						|
            'https://secure.nba.com/assets/amp/include/video/iframe.html', {
 | 
						|
                'contentId': content_id,
 | 
						|
                'team': team,
 | 
						|
            }), NBAEmbedIE.ie_key())
 | 
						|
 | 
						|
    def _call_api(self, team, content_id, query, resource):
 | 
						|
        return self._download_json(
 | 
						|
            'https://api.nba.net/2/%s/video,imported_video,wsc/' % team,
 | 
						|
            content_id, 'Download %s JSON metadata' % resource,
 | 
						|
            query=query, headers={
 | 
						|
                'accessToken': 'internal|bb88df6b4c2244e78822812cecf1ee1b',
 | 
						|
            })['response']['result']
 | 
						|
 | 
						|
    def _extract_video(self, video, team, extract_all=True):
 | 
						|
        video_id = compat_str(video['nid'])
 | 
						|
        team = video['brand']
 | 
						|
 | 
						|
        info = {
 | 
						|
            'id': video_id,
 | 
						|
            'title': video.get('title') or video.get('headline') or video['shortHeadline'],
 | 
						|
            'description': video.get('description'),
 | 
						|
            'timestamp': parse_iso8601(video.get('published')),
 | 
						|
        }
 | 
						|
 | 
						|
        subtitles = {}
 | 
						|
        captions = try_get(video, lambda x: x['videoCaptions']['sidecars'], dict) or {}
 | 
						|
        for caption_url in captions.values():
 | 
						|
            subtitles.setdefault('en', []).append({'url': caption_url})
 | 
						|
 | 
						|
        formats = []
 | 
						|
        mp4_url = video.get('mp4')
 | 
						|
        if mp4_url:
 | 
						|
            formats.append({
 | 
						|
                'url': mp4_url,
 | 
						|
            })
 | 
						|
 | 
						|
        if extract_all:
 | 
						|
            source_url = video.get('videoSource')
 | 
						|
            if source_url and not source_url.startswith('s3://') and self._is_valid_url(source_url, video_id, 'source'):
 | 
						|
                formats.append({
 | 
						|
                    'format_id': 'source',
 | 
						|
                    'url': source_url,
 | 
						|
                    'preference': 1,
 | 
						|
                })
 | 
						|
 | 
						|
            m3u8_url = video.get('m3u8')
 | 
						|
            if m3u8_url:
 | 
						|
                if '.akamaihd.net/i/' in m3u8_url:
 | 
						|
                    formats.extend(self._extract_akamai_formats(
 | 
						|
                        m3u8_url, video_id, {'http': 'pmd.cdn.turner.com'}))
 | 
						|
                else:
 | 
						|
                    formats.extend(self._extract_m3u8_formats(
 | 
						|
                        m3u8_url, video_id, 'mp4',
 | 
						|
                        'm3u8_native', m3u8_id='hls', fatal=False))
 | 
						|
 | 
						|
            content_xml = video.get('contentXml')
 | 
						|
            if team and content_xml:
 | 
						|
                cvp_info = self._extract_nba_cvp_info(
 | 
						|
                    team + content_xml, video_id, fatal=False)
 | 
						|
                if cvp_info:
 | 
						|
                    formats.extend(cvp_info['formats'])
 | 
						|
                    subtitles = self._merge_subtitles(subtitles, cvp_info['subtitles'])
 | 
						|
                    info = merge_dicts(info, cvp_info)
 | 
						|
 | 
						|
            self._sort_formats(formats)
 | 
						|
        else:
 | 
						|
            info.update(self._embed_url_result(team, video['videoId']))
 | 
						|
 | 
						|
        info.update({
 | 
						|
            'formats': formats,
 | 
						|
            'subtitles': subtitles,
 | 
						|
        })
 | 
						|
 | 
						|
        return info
 | 
						|
 | 
						|
    def _real_extract(self, url):
 | 
						|
        team, display_id = re.match(self._VALID_URL, url).groups()
 | 
						|
        if '/play#/' in url:
 | 
						|
            display_id = compat_urllib_parse_unquote(display_id)
 | 
						|
        else:
 | 
						|
            webpage = self._download_webpage(url, display_id)
 | 
						|
            display_id = self._search_regex(
 | 
						|
                self._CONTENT_ID_REGEX + r'\s*:\s*"([^"]+)"', webpage, 'video id')
 | 
						|
        return self._extract_url_results(team, display_id)
 | 
						|
 | 
						|
 | 
						|
class NBAEmbedIE(NBABaseIE):
 | 
						|
    IENAME = 'nba:embed'
 | 
						|
    _VALID_URL = r'https?://secure\.nba\.com/assets/amp/include/video/(?:topI|i)frame\.html\?.*?\bcontentId=(?P<id>[^?#&]+)'
 | 
						|
    _TESTS = [{
 | 
						|
        'url': 'https://secure.nba.com/assets/amp/include/video/topIframe.html?contentId=teams/bulls/2020/12/04/3478774/1607105587854-20201204_SCHEDULE_RELEASE_FINAL_DRUPAL-3478774&team=bulls&adFree=false&profile=71&videoPlayerName=TAMPCVP&baseUrl=&videoAdsection=nba.com_mobile_web_teamsites_chicagobulls&Env=',
 | 
						|
        'only_matching': True,
 | 
						|
    }, {
 | 
						|
        'url': 'https://secure.nba.com/assets/amp/include/video/iframe.html?contentId=2016/10/29/0021600027boschaplay7&adFree=false&profile=71&team=&videoPlayerName=LAMPCVP',
 | 
						|
        'only_matching': True,
 | 
						|
    }]
 | 
						|
 | 
						|
    def _real_extract(self, url):
 | 
						|
        qs = compat_parse_qs(compat_urllib_parse_urlparse(url).query)
 | 
						|
        content_id = qs['contentId'][0]
 | 
						|
        team = qs.get('team', [None])[0]
 | 
						|
        if not team:
 | 
						|
            return self.url_result(
 | 
						|
                'https://watch.nba.com/video/' + content_id, NBAWatchIE.ie_key())
 | 
						|
        video = self._call_api(team, content_id, {'videoid': content_id}, 'video')[0]
 | 
						|
        return self._extract_video(video, team)
 | 
						|
 | 
						|
 | 
						|
class NBAIE(NBABaseIE):
 | 
						|
    IENAME = 'nba'
 | 
						|
    _VALID_URL = NBABaseIE._VALID_URL_BASE + '(?!%s)video/(?P<id>(?:[^/]+/)*[^/?#&]+)' % NBABaseIE._CHANNEL_PATH_REGEX
 | 
						|
    _TESTS = [{
 | 
						|
        'url': 'https://www.nba.com/bulls/video/teams/bulls/2020/12/04/3478774/1607105587854-20201204schedulereleasefinaldrupal-3478774',
 | 
						|
        'info_dict': {
 | 
						|
            'id': '45039',
 | 
						|
            'ext': 'mp4',
 | 
						|
            'title': 'AND WE BACK.',
 | 
						|
            'description': 'Part 1 of our 2020-21 schedule is here! Watch our games on NBC Sports Chicago.',
 | 
						|
            'duration': 94,
 | 
						|
            'timestamp': 1607112000,
 | 
						|
            'upload_date': '20201218',
 | 
						|
        },
 | 
						|
    }, {
 | 
						|
        'url': 'https://www.nba.com/bucks/play#/video/teams%2Fbucks%2F2020%2F12%2F17%2F64860%2F1608252863446-Op_Dream_16x9-64860',
 | 
						|
        'only_matching': True,
 | 
						|
    }, {
 | 
						|
        'url': 'https://www.nba.com/bucks/play#/video/wsc%2Fteams%2F2787C911AA1ACD154B5377F7577CCC7134B2A4B0',
 | 
						|
        'only_matching': True,
 | 
						|
    }]
 | 
						|
    _CONTENT_ID_REGEX = r'videoID'
 | 
						|
 | 
						|
    def _extract_url_results(self, team, content_id):
 | 
						|
        return self._embed_url_result(team, content_id)
 | 
						|
 | 
						|
 | 
						|
class NBAChannelIE(NBABaseIE):
 | 
						|
    IENAME = 'nba:channel'
 | 
						|
    _VALID_URL = NBABaseIE._VALID_URL_BASE + '(?:%s)/(?P<id>[^/?#&]+)' % NBABaseIE._CHANNEL_PATH_REGEX
 | 
						|
    _TESTS = [{
 | 
						|
        'url': 'https://www.nba.com/blazers/video/channel/summer_league',
 | 
						|
        'info_dict': {
 | 
						|
            'title': 'Summer League',
 | 
						|
        },
 | 
						|
        'playlist_mincount': 138,
 | 
						|
    }, {
 | 
						|
        'url': 'https://www.nba.com/bucks/play#/series/On%20This%20Date',
 | 
						|
        'only_matching': True,
 | 
						|
    }]
 | 
						|
    _CONTENT_ID_REGEX = r'videoSubCategory'
 | 
						|
    _PAGE_SIZE = 100
 | 
						|
 | 
						|
    def _fetch_page(self, team, channel, page):
 | 
						|
        results = self._call_api(team, channel, {
 | 
						|
            'channels': channel,
 | 
						|
            'count': self._PAGE_SIZE,
 | 
						|
            'offset': page * self._PAGE_SIZE,
 | 
						|
        }, 'page %d' % (page + 1))
 | 
						|
        for video in results:
 | 
						|
            yield self._extract_video(video, team, False)
 | 
						|
 | 
						|
    def _extract_url_results(self, team, content_id):
 | 
						|
        entries = OnDemandPagedList(
 | 
						|
            functools.partial(self._fetch_page, team, content_id),
 | 
						|
            self._PAGE_SIZE)
 | 
						|
        return self.playlist_result(entries, playlist_title=content_id)
 |