youtube-dl

Another place where youtube-dl lives on
git clone git://git.oshgnacknak.de/youtube-dl.git
Log | Files | Refs | README | LICENSE

redbulltv.py (9332B)


      1 # coding: utf-8
      2 from __future__ import unicode_literals
      3 
      4 import re
      5 
      6 from .common import InfoExtractor
      7 from ..compat import compat_HTTPError
      8 from ..utils import (
      9     float_or_none,
     10     ExtractorError,
     11 )
     12 
     13 
     14 class RedBullTVIE(InfoExtractor):
     15     _VALID_URL = r'https?://(?:www\.)?redbull(?:\.tv|\.com(?:/[^/]+)?(?:/tv)?)(?:/events/[^/]+)?/(?:videos?|live|(?:film|episode)s)/(?P<id>AP-\w+)'
     16     _TESTS = [{
     17         # film
     18         'url': 'https://www.redbull.tv/video/AP-1Q6XCDTAN1W11',
     19         'md5': 'fb0445b98aa4394e504b413d98031d1f',
     20         'info_dict': {
     21             'id': 'AP-1Q6XCDTAN1W11',
     22             'ext': 'mp4',
     23             'title': 'ABC of... WRC - ABC of... S1E6',
     24             'description': 'md5:5c7ed8f4015c8492ecf64b6ab31e7d31',
     25             'duration': 1582.04,
     26         },
     27     }, {
     28         # episode
     29         'url': 'https://www.redbull.tv/video/AP-1PMHKJFCW1W11',
     30         'info_dict': {
     31             'id': 'AP-1PMHKJFCW1W11',
     32             'ext': 'mp4',
     33             'title': 'Grime - Hashtags S2E4',
     34             'description': 'md5:5546aa612958c08a98faaad4abce484d',
     35             'duration': 904,
     36         },
     37         'params': {
     38             'skip_download': True,
     39         },
     40     }, {
     41         'url': 'https://www.redbull.com/int-en/tv/video/AP-1UWHCAR9S1W11/rob-meets-sam-gaze?playlist=playlists::3f81040a-2f31-4832-8e2e-545b1d39d173',
     42         'only_matching': True,
     43     }, {
     44         'url': 'https://www.redbull.com/us-en/videos/AP-1YM9QCYE52111',
     45         'only_matching': True,
     46     }, {
     47         'url': 'https://www.redbull.com/us-en/events/AP-1XV2K61Q51W11/live/AP-1XUJ86FDH1W11',
     48         'only_matching': True,
     49     }, {
     50         'url': 'https://www.redbull.com/int-en/films/AP-1ZSMAW8FH2111',
     51         'only_matching': True,
     52     }, {
     53         'url': 'https://www.redbull.com/int-en/episodes/AP-1TQWK7XE11W11',
     54         'only_matching': True,
     55     }]
     56 
     57     def extract_info(self, video_id):
     58         session = self._download_json(
     59             'https://api.redbull.tv/v3/session', video_id,
     60             note='Downloading access token', query={
     61                 'category': 'personal_computer',
     62                 'os_family': 'http',
     63             })
     64         if session.get('code') == 'error':
     65             raise ExtractorError('%s said: %s' % (
     66                 self.IE_NAME, session['message']))
     67         token = session['token']
     68 
     69         try:
     70             video = self._download_json(
     71                 'https://api.redbull.tv/v3/products/' + video_id,
     72                 video_id, note='Downloading video information',
     73                 headers={'Authorization': token}
     74             )
     75         except ExtractorError as e:
     76             if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404:
     77                 error_message = self._parse_json(
     78                     e.cause.read().decode(), video_id)['error']
     79                 raise ExtractorError('%s said: %s' % (
     80                     self.IE_NAME, error_message), expected=True)
     81             raise
     82 
     83         title = video['title'].strip()
     84 
     85         formats = self._extract_m3u8_formats(
     86             'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (video_id, token),
     87             video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls')
     88         self._sort_formats(formats)
     89 
     90         subtitles = {}
     91         for resource in video.get('resources', []):
     92             if resource.startswith('closed_caption_'):
     93                 splitted_resource = resource.split('_')
     94                 if splitted_resource[2]:
     95                     subtitles.setdefault('en', []).append({
     96                         'url': 'https://resources.redbull.tv/%s/%s' % (video_id, resource),
     97                         'ext': splitted_resource[2],
     98                     })
     99 
    100         subheading = video.get('subheading')
    101         if subheading:
    102             title += ' - %s' % subheading
    103 
    104         return {
    105             'id': video_id,
    106             'title': title,
    107             'description': video.get('long_description') or video.get(
    108                 'short_description'),
    109             'duration': float_or_none(video.get('duration'), scale=1000),
    110             'formats': formats,
    111             'subtitles': subtitles,
    112         }
    113 
    114     def _real_extract(self, url):
    115         video_id = self._match_id(url)
    116         return self.extract_info(video_id)
    117 
    118 
    119 class RedBullEmbedIE(RedBullTVIE):
    120     _VALID_URL = r'https?://(?:www\.)?redbull\.com/embed/(?P<id>rrn:content:[^:]+:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}:[a-z]{2}-[A-Z]{2,3})'
    121     _TESTS = [{
    122         # HLS manifest accessible only using assetId
    123         'url': 'https://www.redbull.com/embed/rrn:content:episode-videos:f3021f4f-3ed4-51ac-915a-11987126e405:en-INT',
    124         'only_matching': True,
    125     }]
    126     _VIDEO_ESSENSE_TMPL = '''... on %s {
    127       videoEssence {
    128         attributes
    129       }
    130     }'''
    131 
    132     def _real_extract(self, url):
    133         rrn_id = self._match_id(url)
    134         asset_id = self._download_json(
    135             'https://edge-graphql.crepo-production.redbullaws.com/v1/graphql',
    136             rrn_id, headers={
    137                 'Accept': 'application/json',
    138                 'API-KEY': 'e90a1ff11335423998b100c929ecc866',
    139             }, query={
    140                 'query': '''{
    141   resource(id: "%s", enforceGeoBlocking: false) {
    142     %s
    143     %s
    144   }
    145 }''' % (rrn_id, self._VIDEO_ESSENSE_TMPL % 'LiveVideo', self._VIDEO_ESSENSE_TMPL % 'VideoResource'),
    146             })['data']['resource']['videoEssence']['attributes']['assetId']
    147         return self.extract_info(asset_id)
    148 
    149 
    150 class RedBullTVRrnContentIE(InfoExtractor):
    151     _VALID_URL = r'https?://(?:www\.)?redbull\.com/(?P<region>[a-z]{2,3})-(?P<lang>[a-z]{2})/tv/(?:video|live|film)/(?P<id>rrn:content:[^:]+:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
    152     _TESTS = [{
    153         'url': 'https://www.redbull.com/int-en/tv/video/rrn:content:live-videos:e3e6feb4-e95f-50b7-962a-c70f8fd13c73/mens-dh-finals-fort-william',
    154         'only_matching': True,
    155     }, {
    156         'url': 'https://www.redbull.com/int-en/tv/video/rrn:content:videos:a36a0f36-ff1b-5db8-a69d-ee11a14bf48b/tn-ts-style?playlist=rrn:content:event-profiles:83f05926-5de8-5389-b5e4-9bb312d715e8:extras',
    157         'only_matching': True,
    158     }, {
    159         'url': 'https://www.redbull.com/int-en/tv/film/rrn:content:films:d1f4d00e-4c04-5d19-b510-a805ffa2ab83/follow-me',
    160         'only_matching': True,
    161     }]
    162 
    163     def _real_extract(self, url):
    164         region, lang, rrn_id = re.search(self._VALID_URL, url).groups()
    165         rrn_id += ':%s-%s' % (lang, region.upper())
    166         return self.url_result(
    167             'https://www.redbull.com/embed/' + rrn_id,
    168             RedBullEmbedIE.ie_key(), rrn_id)
    169 
    170 
    171 class RedBullIE(InfoExtractor):
    172     _VALID_URL = r'https?://(?:www\.)?redbull\.com/(?P<region>[a-z]{2,3})-(?P<lang>[a-z]{2})/(?P<type>(?:episode|film|(?:(?:recap|trailer)-)?video)s|live)/(?!AP-|rrn:content:)(?P<id>[^/?#&]+)'
    173     _TESTS = [{
    174         'url': 'https://www.redbull.com/int-en/episodes/grime-hashtags-s02-e04',
    175         'md5': 'db8271a7200d40053a1809ed0dd574ff',
    176         'info_dict': {
    177             'id': 'AA-1MT8DQWA91W14',
    178             'ext': 'mp4',
    179             'title': 'Grime - Hashtags S2E4',
    180             'description': 'md5:5546aa612958c08a98faaad4abce484d',
    181         },
    182     }, {
    183         'url': 'https://www.redbull.com/int-en/films/kilimanjaro-mountain-of-greatness',
    184         'only_matching': True,
    185     }, {
    186         'url': 'https://www.redbull.com/int-en/recap-videos/uci-mountain-bike-world-cup-2017-mens-xco-finals-from-vallnord',
    187         'only_matching': True,
    188     }, {
    189         'url': 'https://www.redbull.com/int-en/trailer-videos/kings-of-content',
    190         'only_matching': True,
    191     }, {
    192         'url': 'https://www.redbull.com/int-en/videos/tnts-style-red-bull-dance-your-style-s1-e12',
    193         'only_matching': True,
    194     }, {
    195         'url': 'https://www.redbull.com/int-en/live/mens-dh-finals-fort-william',
    196         'only_matching': True,
    197     }, {
    198         # only available on the int-en website so a fallback is need for the API
    199         # https://www.redbull.com/v3/api/graphql/v1/v3/query/en-GB>en-INT?filter[uriSlug]=fia-wrc-saturday-recap-estonia&rb3Schema=v1:hero
    200         'url': 'https://www.redbull.com/gb-en/live/fia-wrc-saturday-recap-estonia',
    201         'only_matching': True,
    202     }]
    203     _INT_FALLBACK_LIST = ['de', 'en', 'es', 'fr']
    204     _LAT_FALLBACK_MAP = ['ar', 'bo', 'car', 'cl', 'co', 'mx', 'pe']
    205 
    206     def _real_extract(self, url):
    207         region, lang, filter_type, display_id = re.search(self._VALID_URL, url).groups()
    208         if filter_type == 'episodes':
    209             filter_type = 'episode-videos'
    210         elif filter_type == 'live':
    211             filter_type = 'live-videos'
    212 
    213         regions = [region.upper()]
    214         if region != 'int':
    215             if region in self._LAT_FALLBACK_MAP:
    216                 regions.append('LAT')
    217             if lang in self._INT_FALLBACK_LIST:
    218                 regions.append('INT')
    219         locale = '>'.join(['%s-%s' % (lang, reg) for reg in regions])
    220 
    221         rrn_id = self._download_json(
    222             'https://www.redbull.com/v3/api/graphql/v1/v3/query/' + locale,
    223             display_id, query={
    224                 'filter[type]': filter_type,
    225                 'filter[uriSlug]': display_id,
    226                 'rb3Schema': 'v1:hero',
    227             })['data']['id']
    228 
    229         return self.url_result(
    230             'https://www.redbull.com/embed/' + rrn_id,
    231             RedBullEmbedIE.ie_key(), rrn_id)