From 8682f760f5d80aefd0086d2dcfb04b38ec5fecb8 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Fri, 9 Aug 2019 16:40:21 -0700 Subject: [PATCH 01/18] [redbulltv] Support .com w/ rrn ID when api.redbull.tv/v3/products/ works URLs that now work: https://www.redbull.com/int-en/episodes/AP-1PMHKJFCW1W11 (working api.redbull.tv/v3/products/, "AP-..." ID) https://www.redbull.com/us-en/events/AP-1XJS6EYM12111/live/AP-1YM8YVH1D2111 (working api.redbull.tv/v3/products/, "rrn:..." ID) URLs that fail: https://www.redbull.com/int-en/films/AP-1ZSMAW8FH2111 (failing api.redbull.tv/v3/products/, "rrn:..." ID) --- youtube_dl/extractor/redbulltv.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index dbe1aaded..2179133ca 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -10,7 +10,7 @@ from ..utils import ( class RedBullTVIE(InfoExtractor): - _VALID_URL = r'https?://(?:www\.)?redbull(?:\.tv|\.com(?:/[^/]+)?(?:/tv)?)(?:/events/[^/]+)?/(?:videos?|live)/(?PAP-\w+)' + _VALID_URL = r'https?://(?:www\.)?redbull\.com/[^/]+/(?:videos|recap-videos|events|episodes|films)/(?PAP-\w+)' _TESTS = [{ # film 'url': 'https://www.redbull.tv/video/AP-1Q6XCDTAN1W11', @@ -76,6 +76,12 @@ class RedBullTVIE(InfoExtractor): title = video['title'].strip() + # use an 'rrn:...' ID instead of an 'AP-...' ID if necessary + content_path = video.get('status', {}).get('play') + if content_path: + # trim '/content/' from '/content/rrn:...' + video_id = content_path[9:] + formats = self._extract_m3u8_formats( 'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (video_id, token), video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls') From 6cb557711b7eb11519349690dc0097d425adcd66 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Fri, 9 Aug 2019 21:38:14 -0700 Subject: [PATCH 02/18] [redbulltv] Use rrn ID for all video downloads All tests from 8682f76 work. --- youtube_dl/extractor/redbulltv.py | 95 ++++++++++++++++--------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 2179133ca..aa4013952 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -2,11 +2,9 @@ from __future__ import unicode_literals from .common import InfoExtractor -from ..compat import compat_HTTPError -from ..utils import ( - float_or_none, - ExtractorError, -) +from ..utils import RegexNotFoundError +import json +import time class RedBullTVIE(InfoExtractor): @@ -47,8 +45,52 @@ class RedBullTVIE(InfoExtractor): }] def _real_extract(self, url): + # video_id is "AP-..." ID video_id = self._match_id(url) + # Try downloading the webpage multiple times in order to get a repsonse + # cache which will contain the result of a query to + # 'https://www.redbull.com/v3/api/composition/v3/query/en-INT?rb3Schema=v1:pageConfig&filter[uriSlug]=%s' % video_id + # We use the response cache to get the rrn ID and other metadata. We do + # this instead of simply querying the API in order to preserve the + # provided URL's locale. (Annoyingly, the locale in the input URL + # ('en-us', for example) is of a different format than the locale + # required for the API request.) + tries = 3 + for i in range(tries): + try: + if i == 0: + webpage = self._download_webpage(url, video_id) + else: + webpage = self._download_webpage(url, video_id, note='Redownloading webpage') + # extract response cache + response_cache = json.loads(self._html_search_regex(r'', webpage, 'response-cache')) + break + except RegexNotFoundError: + if i < tries - 1: + self.to_screen('Waiting before redownloading webpage') + time.sleep(2) + else: + raise + + # select the key that includes the string 'pageConfig' + metadata = json.loads( + response_cache[ + [key for key in response_cache.keys() if 'pageConfig' in key][0] + ]['response'] + )['data'] + + # extract rrn ID + rrn_id_ext = metadata['analytics']['asset']['trackingDimensions']['masterID'] + # trim locale from the end of rrn_id_ext + rrn_id = ':'.join(rrn_id_ext.split(':')[:-1]) + + # extract metadata + title = metadata['analytics']['asset']['title'] + short_description = metadata['pageMeta']['og:title'] + long_description = metadata['pageMeta']['og:description'] + + # get access token for download session = self._download_json( 'https://api.redbull.tv/v3/session', video_id, note='Downloading access token', query={ @@ -60,55 +102,16 @@ class RedBullTVIE(InfoExtractor): self.IE_NAME, session['message'])) token = session['token'] - try: - video = self._download_json( - 'https://api.redbull.tv/v3/products/' + video_id, - video_id, note='Downloading video information', - headers={'Authorization': token} - ) - except ExtractorError as e: - if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404: - error_message = self._parse_json( - e.cause.read().decode(), video_id)['error'] - raise ExtractorError('%s said: %s' % ( - self.IE_NAME, error_message), expected=True) - raise - - title = video['title'].strip() - - # use an 'rrn:...' ID instead of an 'AP-...' ID if necessary - content_path = video.get('status', {}).get('play') - if content_path: - # trim '/content/' from '/content/rrn:...' - video_id = content_path[9:] - formats = self._extract_m3u8_formats( - 'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (video_id, token), + 'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (rrn_id, token), video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls') self._sort_formats(formats) - subtitles = {} - for resource in video.get('resources', []): - if resource.startswith('closed_caption_'): - splitted_resource = resource.split('_') - if splitted_resource[2]: - subtitles.setdefault('en', []).append({ - 'url': 'https://resources.redbull.tv/%s/%s' % (video_id, resource), - 'ext': splitted_resource[2], - }) - - subheading = video.get('subheading') - if subheading: - title += ' - %s' % subheading - return { 'id': video_id, 'title': title, - 'description': video.get('long_description') or video.get( - 'short_description'), - 'duration': float_or_none(video.get('duration'), scale=1000), + 'description': long_description or short_description, 'formats': formats, - 'subtitles': subtitles, } From 32918652df080ff426fca62cb455dc8cb0e6cecb Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Fri, 9 Aug 2019 22:20:59 -0700 Subject: [PATCH 03/18] [redbulltv] Remove RedBullTVRrnContentIE extractor The test cases either are dead links or redirect to a URL that RedBullTVIE handles. AFAIK, the only usage of rrn in URLs is now for indicating playlists (rrn:content:collections:...), whose current video is already handled by RedBullTVIE. Playlist support is not currently implemented for this domain, and RedBullTVRrnContentIE is never used with current redbulltv URLs. --- youtube_dl/extractor/extractors.py | 5 +---- youtube_dl/extractor/redbulltv.py | 22 ---------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/youtube_dl/extractor/extractors.py b/youtube_dl/extractor/extractors.py index 06de556b7..211eed6b7 100644 --- a/youtube_dl/extractor/extractors.py +++ b/youtube_dl/extractor/extractors.py @@ -926,10 +926,7 @@ from .raywenderlich import ( ) from .rbmaradio import RBMARadioIE from .rds import RDSIE -from .redbulltv import ( - RedBullTVIE, - RedBullTVRrnContentIE, -) +from .redbulltv import RedBullTVIE from .reddit import ( RedditIE, RedditRIE, diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index aa4013952..bd98fda1d 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -113,25 +113,3 @@ class RedBullTVIE(InfoExtractor): 'description': long_description or short_description, 'formats': formats, } - - -class RedBullTVRrnContentIE(InfoExtractor): - _VALID_URL = r'https?://(?:www\.)?redbull(?:\.tv|\.com(?:/[^/]+)?(?:/tv)?)/(?:video|live)/rrn:content:[^:]+:(?P[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})' - _TESTS = [{ - 'url': 'https://www.redbull.com/int-en/tv/video/rrn:content:live-videos:e3e6feb4-e95f-50b7-962a-c70f8fd13c73/mens-dh-finals-fort-william', - 'only_matching': True, - }, { - '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', - 'only_matching': True, - }] - - def _real_extract(self, url): - display_id = self._match_id(url) - - webpage = self._download_webpage(url, display_id) - - video_url = self._og_search_url(webpage) - - return self.url_result( - video_url, ie=RedBullTVIE.ie_key(), - video_id=RedBullTVIE._match_id(video_url)) From e07e8045230372c4eb14eb6a3f6b81cbffde4ce6 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Fri, 9 Aug 2019 23:49:19 -0700 Subject: [PATCH 04/18] [redbulltv] Fix/add tests --- youtube_dl/extractor/redbulltv.py | 65 +++++++++++++++++++------------ 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index bd98fda1d..cfb92e981 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -8,40 +8,57 @@ import time class RedBullTVIE(InfoExtractor): - _VALID_URL = r'https?://(?:www\.)?redbull\.com/[^/]+/(?:videos|recap-videos|events|episodes|films)/(?PAP-\w+)' + _VALID_URL = r'https?://(?:www\.)?redbull\.com/[^/]+/(?:videos|recap-videos|events|episodes|films)/(?PAP-\w+)(?:/live/AP-\w+)?(?:\?playlist)?(?:\?playlistId=rrn:content:collections:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}:[\w-]+)?' _TESTS = [{ - # film - 'url': 'https://www.redbull.tv/video/AP-1Q6XCDTAN1W11', - 'md5': 'fb0445b98aa4394e504b413d98031d1f', + # videos + 'url': 'https://www.redbull.com/int-en/videos/AP-1YM911N612111', + 'md5': 'e2d92baecce184ecd521fa3d72f36aa8', 'info_dict': { - 'id': 'AP-1Q6XCDTAN1W11', + 'id': 'AP-1YM911N612111', 'ext': 'mp4', - 'title': 'ABC of... WRC - ABC of... S1E6', - 'description': 'md5:5c7ed8f4015c8492ecf64b6ab31e7d31', - 'duration': 1582.04, + 'title': 'Preview the Lenzerheide DH course with Gee Atherton\'s POV', + 'description': 'md5:99b46ed1e2abb02c4c6f6113cff13ba4', }, }, { - # episode - 'url': 'https://www.redbull.tv/video/AP-1PMHKJFCW1W11', + # recap-videos + 'url': 'https://www.redbull.com/int-en/recap-videos/AP-1YM8YXTC52111?playlistId=rrn:content:collections:e916768e-7b47-413d-a254-bc97d7f808f7:en-INT', + 'md5': 'aa7c6ab92ea6103f61d5fc5cbb85fd53', + 'info_dict': { + 'id': 'AP-1YM8YXTC52111', + 'ext': 'mp4', + 'title': 'Val di Sole DH recap', + 'description': 'md5:df0fd44b4d1a396a692998fc395b75b8', + }, + }, { + # events + 'url': 'https://www.redbull.com/int-en/recap-videos/AP-1ZYQN7WNW2111', + 'md5': '0f2043deef92405249c8ca96ba197901', + 'info_dict': { + 'id': 'AP-1ZYQN7WNW2111', + 'ext': 'mp4', + 'title': 'Jokkis Race', + 'description': 'md5:dc2be9d7b3e7048967468d39a889a5e1', + }, + }, { + # episodes + 'url': 'https://www.redbull.com/int-en/episodes/AP-1PMHKJFCW1W11', + 'md5': 'db8271a7200d40053a1809ed0dd574ff', 'info_dict': { 'id': 'AP-1PMHKJFCW1W11', 'ext': 'mp4', - 'title': 'Grime - Hashtags S2E4', - 'description': 'md5:b5f522b89b72e1e23216e5018810bb25', - 'duration': 904.6, - }, - 'params': { - 'skip_download': True, + 'title': 'Grime', + 'description': 'md5:7b4bdf2edd53d6c0c5e2e336c02e6fbb', }, }, { - 'url': 'https://www.redbull.com/int-en/tv/video/AP-1UWHCAR9S1W11/rob-meets-sam-gaze?playlist=playlists::3f81040a-2f31-4832-8e2e-545b1d39d173', - 'only_matching': True, - }, { - 'url': 'https://www.redbull.com/us-en/videos/AP-1YM9QCYE52111', - 'only_matching': True, - }, { - 'url': 'https://www.redbull.com/us-en/events/AP-1XV2K61Q51W11/live/AP-1XUJ86FDH1W11', - 'only_matching': True, + # films + 'url': 'https://www.redbull.com/int-en/films/AP-1ZSMAW8FH2111', + 'md5': '3a753f7c3c1f9966ae660e05c3c7862b', + 'info_dict': { + 'id': 'AP-1ZSMAW8FH2111', + 'ext': 'mp4', + 'title': 'Against the Odds', + 'description': 'md5:6db1cf4c4f85442a91f4d9cd03b7f4e3', + }, }] def _real_extract(self, url): From af37bb42ab5c563ddab28e0e0b6620c40ddc3935 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 12:41:05 -0700 Subject: [PATCH 05/18] [redbulltv] Pull (most) metadata from products API instead of JSON cache Adds support for duration, release_data metadata. Re-implements concatenating subheading onto title if present. --- youtube_dl/extractor/redbulltv.py | 65 +++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index cfb92e981..59f705b05 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -2,7 +2,10 @@ from __future__ import unicode_literals from .common import InfoExtractor -from ..utils import RegexNotFoundError +from ..utils import ( + RegexNotFoundError, + float_or_none, +) import json import time @@ -16,8 +19,10 @@ class RedBullTVIE(InfoExtractor): 'info_dict': { 'id': 'AP-1YM911N612111', 'ext': 'mp4', - 'title': 'Preview the Lenzerheide DH course with Gee Atherton\'s POV', - 'description': 'md5:99b46ed1e2abb02c4c6f6113cff13ba4', + 'title': 'md5:fa027630eb511593fe91e4323762e95d', + 'description': 'md5:7f769874c63e45f9b6f43315a99094c7', + 'duration': 255.0, + 'release_date': '20190809', }, }, { # recap-videos @@ -26,8 +31,10 @@ class RedBullTVIE(InfoExtractor): 'info_dict': { 'id': 'AP-1YM8YXTC52111', 'ext': 'mp4', - 'title': 'Val di Sole DH recap', - 'description': 'md5:df0fd44b4d1a396a692998fc395b75b8', + 'title': 'md5:dc9aec63e687a534a6bb13adbb86571c', + 'description': 'md5:3774af48bf6fbc5fb6c8ebad6891f728', + 'duration': 1560.0, + 'release_date': '20190808', }, }, { # events @@ -36,8 +43,10 @@ class RedBullTVIE(InfoExtractor): 'info_dict': { 'id': 'AP-1ZYQN7WNW2111', 'ext': 'mp4', - 'title': 'Jokkis Race', - 'description': 'md5:dc2be9d7b3e7048967468d39a889a5e1', + 'title': 'md5:c2a490a9db25823c2c9790093e3563ab', + 'description': 'md5:fb7e7a8cfaa72f7dc139238186d69800', + 'duration': 933.0, + 'release_date': '20190727', }, }, { # episodes @@ -46,8 +55,10 @@ class RedBullTVIE(InfoExtractor): 'info_dict': { 'id': 'AP-1PMHKJFCW1W11', 'ext': 'mp4', - 'title': 'Grime', - 'description': 'md5:7b4bdf2edd53d6c0c5e2e336c02e6fbb', + 'title': 'md5:f767c9809c12c3411632cb7de9d30608', + 'description': 'md5:b5f522b89b72e1e23216e5018810bb25', + 'duration': 904.0, + 'release_date': '20170221', }, }, { # films @@ -56,13 +67,15 @@ class RedBullTVIE(InfoExtractor): 'info_dict': { 'id': 'AP-1ZSMAW8FH2111', 'ext': 'mp4', - 'title': 'Against the Odds', - 'description': 'md5:6db1cf4c4f85442a91f4d9cd03b7f4e3', + 'title': 'md5:47478de1e62dadcda748c2b58ae7e343', + 'description': 'md5:9a885f6f5344b98c684f8aaf6bdfbc38', + 'duration': 4837.0, + 'release_date': '20190801', }, }] def _real_extract(self, url): - # video_id is "AP-..." ID + # video_id is 'AP-...' ID video_id = self._match_id(url) # Try downloading the webpage multiple times in order to get a repsonse @@ -102,11 +115,6 @@ class RedBullTVIE(InfoExtractor): # trim locale from the end of rrn_id_ext rrn_id = ':'.join(rrn_id_ext.split(':')[:-1]) - # extract metadata - title = metadata['analytics']['asset']['title'] - short_description = metadata['pageMeta']['og:title'] - long_description = metadata['pageMeta']['og:description'] - # get access token for download session = self._download_json( 'https://api.redbull.tv/v3/session', video_id, @@ -119,14 +127,37 @@ class RedBullTVIE(InfoExtractor): self.IE_NAME, session['message'])) token = session['token'] + # extract formats from m3u8 + # subtitle tracks are also listed in this m3u8, but yt-dl does not + # currently implement an easy way to download m3u8 VTT subtitles formats = self._extract_m3u8_formats( 'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (rrn_id, token), video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls') self._sort_formats(formats) + # download more metadata + metadata2 = self._download_json( + 'https://api.redbull.tv/v3/products/%s' % rrn_id, + video_id, note='Downloading video information', + headers={'Authorization': token} + ) + + # extract metadata + title = metadata2['title'].strip() + subheading = metadata2.get('subheading') + if subheading: + title += ' - %s' % subheading + long_description = metadata2.get('long_description') + short_description = metadata2.get('short_description') + duration = float_or_none(metadata2.get('duration'), scale=1000) + release_date = metadata['analytics']['asset']['publishDate'][:10] \ + .replace('-', '') + return { 'id': video_id, 'title': title, 'description': long_description or short_description, + 'duration': duration, + 'release_date': release_date, 'formats': formats, } From 77fb1cef0cb9302ac94482744802fca3bc692d99 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 12:42:02 -0700 Subject: [PATCH 06/18] [redbulltv] Add user message on failing to dl/locate response cache --- youtube_dl/extractor/redbulltv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 59f705b05..acb16bb18 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -101,6 +101,7 @@ class RedBullTVIE(InfoExtractor): self.to_screen('Waiting before redownloading webpage') time.sleep(2) else: + self.to_screen('Failed to download/locate response cache. Wait a few seconds and try running the command again.') raise # select the key that includes the string 'pageConfig' From 262b8aecd64184ae4e0104fece9351d4a064aeca Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 12:56:03 -0700 Subject: [PATCH 07/18] [redbulltv] Change [key] to .get(key) to comply with coding conventions (For optional keys only) --- youtube_dl/extractor/redbulltv.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index acb16bb18..d48593878 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -148,11 +148,15 @@ class RedBullTVIE(InfoExtractor): subheading = metadata2.get('subheading') if subheading: title += ' - %s' % subheading + long_description = metadata2.get('long_description') short_description = metadata2.get('short_description') + duration = float_or_none(metadata2.get('duration'), scale=1000) - release_date = metadata['analytics']['asset']['publishDate'][:10] \ - .replace('-', '') + + release_date = metadata.get('analytics', {}).get('asset', {}).get('publishDate') + if release_date: + release_date = release_date[:10].replace('-', '') return { 'id': video_id, From 88d61c8c14e2ff984f5589b04228665cebf3a22b Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 13:14:39 -0700 Subject: [PATCH 08/18] [redbulltv] Fix cache retry system printing error multiple times --- youtube_dl/extractor/redbulltv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index d48593878..49935466f 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -95,14 +95,15 @@ class RedBullTVIE(InfoExtractor): webpage = self._download_webpage(url, video_id, note='Redownloading webpage') # extract response cache response_cache = json.loads(self._html_search_regex(r'', webpage, 'response-cache')) - break except RegexNotFoundError: if i < tries - 1: self.to_screen('Waiting before redownloading webpage') time.sleep(2) + continue else: self.to_screen('Failed to download/locate response cache. Wait a few seconds and try running the command again.') raise + break # select the key that includes the string 'pageConfig' metadata = json.loads( From 0702ec823530f1be60e7c7fb4e52c8ab0b0e678c Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 13:44:14 -0700 Subject: [PATCH 09/18] [redbulltv] Add redundancy for title, short_desc, and release_date --- youtube_dl/extractor/redbulltv.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 49935466f..7e77e0152 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -145,19 +145,30 @@ class RedBullTVIE(InfoExtractor): ) # extract metadata - title = metadata2['title'].strip() + title = metadata2.get('title').strip() or \ + metadata.get('analytics', {}).get('asset', {}).get(['title']) + subheading = metadata2.get('subheading') if subheading: title += ' - %s' % subheading long_description = metadata2.get('long_description') - short_description = metadata2.get('short_description') + short_description = metadata2.get('short_description') or \ + metadata['pageMeta']['og:description'] duration = float_or_none(metadata2.get('duration'), scale=1000) - release_date = metadata.get('analytics', {}).get('asset', {}).get('publishDate') - if release_date: - release_date = release_date[:10].replace('-', '') + release_dates = [metadata.get('analytics', {}).get('asset', {}) \ + .get('publishDate')] + release_dates.append(metadata.get('analytics', {}).get('asset', {}) \ + .get('trackingDimensions', {}).get('publishingDate')) + + if release_dates[0]: + release_date = release_dates[0][:10].replace('-', '') + elif release_dates[1]: + release_date = ''.join(release_dates[1].split('-')[::-1]) + else: + release_date = None return { 'id': video_id, From 9dec82e6b63ab6c71c05bcf747ee732fcaba7dbd Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 14:05:48 -0700 Subject: [PATCH 10/18] [redbulltv] Rewrite _VALID_URL regex for readability --- youtube_dl/extractor/redbulltv.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 7e77e0152..3b5651fb8 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -11,7 +11,14 @@ import time class RedBullTVIE(InfoExtractor): - _VALID_URL = r'https?://(?:www\.)?redbull\.com/[^/]+/(?:videos|recap-videos|events|episodes|films)/(?PAP-\w+)(?:/live/AP-\w+)?(?:\?playlist)?(?:\?playlistId=rrn:content:collections:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}:[\w-]+)?' + _VALID_URL = r"""(?x)^ + https?:// + (?:www\.)?redbull\.com/ + [^/]+/ # locale/language code + (?:videos|recap-videos|events|episodes|films)/ + (?PAP-\w{13})(?:/live/AP-\w{13})? + (?:\?playlist)?(?:\?playlistId=rrn:content:collections:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}:[\w-]+)? + $""" _TESTS = [{ # videos 'url': 'https://www.redbull.com/int-en/videos/AP-1YM911N612111', From 5ab803e4f542bb7e5c316287536f5e62b9d88ca3 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 14:41:28 -0700 Subject: [PATCH 11/18] [redbulltv] Adhere to soft 80 char limit --- youtube_dl/extractor/redbulltv.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 3b5651fb8..ec5574347 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -99,9 +99,12 @@ class RedBullTVIE(InfoExtractor): if i == 0: webpage = self._download_webpage(url, video_id) else: - webpage = self._download_webpage(url, video_id, note='Redownloading webpage') + webpage = self._download_webpage(url, video_id, + note='Redownloading webpage') # extract response cache - response_cache = json.loads(self._html_search_regex(r'', webpage, 'response-cache')) + response_cache = json.loads(self._html_search_regex( + r'', + webpage, 'response-cache')) except RegexNotFoundError: if i < tries - 1: self.to_screen('Waiting before redownloading webpage') From b225621f588a28f53bb9aee1b7f866e6db4c1616 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 15:33:15 -0700 Subject: [PATCH 12/18] [redbulltv] Use to try_get for optional metadata --- youtube_dl/extractor/redbulltv.py | 35 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index ec5574347..6ba1747f5 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -2,8 +2,10 @@ from __future__ import unicode_literals from .common import InfoExtractor +from ..compat import compat_str from ..utils import ( RegexNotFoundError, + try_get, float_or_none, ) import json @@ -155,28 +157,35 @@ class RedBullTVIE(InfoExtractor): ) # extract metadata - title = metadata2.get('title').strip() or \ - metadata.get('analytics', {}).get('asset', {}).get(['title']) + title = try_get(metadata2, lambda x: x['title'], compat_str) or \ + try_get(metadata, lambda x: x['analytics']['asset']['title'], compat_str) - subheading = metadata2.get('subheading') + subheading = try_get(metadata2, lambda x: x['subheading'], compat_str) if subheading: title += ' - %s' % subheading - long_description = metadata2.get('long_description') - short_description = metadata2.get('short_description') or \ - metadata['pageMeta']['og:description'] + long_description = try_get(metadata2, lambda x: x['long_description'], compat_str) + short_description = try_get(metadata2, lambda x: x['short_description'], compat_str) or \ + try_get(metadata, lambda x: x['pageMeta']['og:description'], + compat_str) - duration = float_or_none(metadata2.get('duration'), scale=1000) + duration = float_or_none(try_get(metadata2, lambda x: x['duration'], int), + scale=1000) - release_dates = [metadata.get('analytics', {}).get('asset', {}) \ - .get('publishDate')] - release_dates.append(metadata.get('analytics', {}).get('asset', {}) \ - .get('trackingDimensions', {}).get('publishingDate')) + release_dates = [try_get(metadata, + lambda x: x['analytics']['asset']['publishDate'], compat_str)] + release_dates.append(try_get(metadata, + lambda x: x['analytics']['asset']['trackingDimensions']['originalPublishingDate'], + compat_str)) + release_dates.append(try_get(metadata, + lambda x: x['analytics']['asset']['trackingDimensions']['publishingDate'], + compat_str)) if release_dates[0]: release_date = release_dates[0][:10].replace('-', '') - elif release_dates[1]: - release_date = ''.join(release_dates[1].split('-')[::-1]) + elif release_dates[1] or release_dates[2]: + release_date = ''.join((release_dates[1] or release_dates[2]) + .split('-')[::-1]) else: release_date = None From 63330b8a252e5dc96e6ec692e72fd497a38a5d7a Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 15:43:42 -0700 Subject: [PATCH 13/18] [redbulltv] Use unified_strdate for date parsing --- youtube_dl/extractor/redbulltv.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 6ba1747f5..3fb04e687 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -7,6 +7,7 @@ from ..utils import ( RegexNotFoundError, try_get, float_or_none, + unified_strdate, ) import json import time @@ -181,13 +182,8 @@ class RedBullTVIE(InfoExtractor): lambda x: x['analytics']['asset']['trackingDimensions']['publishingDate'], compat_str)) - if release_dates[0]: - release_date = release_dates[0][:10].replace('-', '') - elif release_dates[1] or release_dates[2]: - release_date = ''.join((release_dates[1] or release_dates[2]) - .split('-')[::-1]) - else: - release_date = None + release_date = unified_strdate(release_dates[0] or release_dates[1] or \ + release_dates[2]) return { 'id': video_id, From 87e48ff02cbf66306ce038fadbf70078d91863c4 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 16:00:26 -0700 Subject: [PATCH 14/18] [redbulltv] Add missing import --- youtube_dl/extractor/redbulltv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 3fb04e687..534cbc6cb 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -5,6 +5,7 @@ from .common import InfoExtractor from ..compat import compat_str from ..utils import ( RegexNotFoundError, + ExtractorError, try_get, float_or_none, unified_strdate, From 54ccc55860aff9d3d7c7fa97c06df2c17df00ce8 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sat, 10 Aug 2019 16:00:47 -0700 Subject: [PATCH 15/18] [redbulltv] flake8 compliance --- youtube_dl/extractor/redbulltv.py | 43 +++++++++++++++++-------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 534cbc6cb..3d7129b04 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -90,11 +90,11 @@ class RedBullTVIE(InfoExtractor): video_id = self._match_id(url) # Try downloading the webpage multiple times in order to get a repsonse - # cache which will contain the result of a query to + # cache which will contain the result of a query to # 'https://www.redbull.com/v3/api/composition/v3/query/en-INT?rb3Schema=v1:pageConfig&filter[uriSlug]=%s' % video_id # We use the response cache to get the rrn ID and other metadata. We do # this instead of simply querying the API in order to preserve the - # provided URL's locale. (Annoyingly, the locale in the input URL + # provided URL's locale. (Annoyingly, the locale in the input URL # ('en-us', for example) is of a different format than the locale # required for the API request.) tries = 3 @@ -103,8 +103,8 @@ class RedBullTVIE(InfoExtractor): if i == 0: webpage = self._download_webpage(url, video_id) else: - webpage = self._download_webpage(url, video_id, - note='Redownloading webpage') + webpage = self._download_webpage( + url, video_id, note='Redownloading webpage') # extract response cache response_cache = json.loads(self._html_search_regex( r'', @@ -121,10 +121,10 @@ class RedBullTVIE(InfoExtractor): # select the key that includes the string 'pageConfig' metadata = json.loads( - response_cache[ - [key for key in response_cache.keys() if 'pageConfig' in key][0] - ]['response'] - )['data'] + response_cache[ + [key for key in response_cache.keys() if 'pageConfig' in key][0] + ]['response'] + )['data'] # extract rrn ID rrn_id_ext = metadata['analytics']['asset']['trackingDimensions']['masterID'] @@ -167,24 +167,29 @@ class RedBullTVIE(InfoExtractor): title += ' - %s' % subheading long_description = try_get(metadata2, lambda x: x['long_description'], compat_str) - short_description = try_get(metadata2, lambda x: x['short_description'], compat_str) or \ - try_get(metadata, lambda x: x['pageMeta']['og:description'], - compat_str) + short_description = try_get( + metadata2, lambda x: x['short_description'], compat_str) or \ + try_get( + metadata, lambda x: x['pageMeta']['og:description'], compat_str) - duration = float_or_none(try_get(metadata2, lambda x: x['duration'], int), - scale=1000) + duration = float_or_none( + try_get(metadata2, lambda x: x['duration'], int), scale=1000) - release_dates = [try_get(metadata, - lambda x: x['analytics']['asset']['publishDate'], compat_str)] - release_dates.append(try_get(metadata, + release_dates = [try_get( + metadata, + lambda x: x['analytics']['asset']['publishDate'], + compat_str)] + release_dates.append(try_get( + metadata, lambda x: x['analytics']['asset']['trackingDimensions']['originalPublishingDate'], compat_str)) - release_dates.append(try_get(metadata, + release_dates.append(try_get( + metadata, lambda x: x['analytics']['asset']['trackingDimensions']['publishingDate'], compat_str)) - release_date = unified_strdate(release_dates[0] or release_dates[1] or \ - release_dates[2]) + release_date = unified_strdate( + release_dates[0] or release_dates[1] or release_dates[2]) return { 'id': video_id, From 13d64407173fbfb720fe90a01f0d38d0128323fc Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Mon, 27 Jul 2020 04:02:26 -0700 Subject: [PATCH 16/18] [redbulltv] Avoid cache-based method and refactor Big thanks to @heyjustyn for finding embedUrl. Token query flag removed (doesn't seem to be sent in browser anymore). I'm not sure about the standard for video_id. I'd guess that one should not change it throughout _real_extract, but I'm not sure of a better way if we desire to use rrn_id as the video_id (it seems to be the best identifier). --- youtube_dl/extractor/redbulltv.py | 106 ++++++++---------------------- 1 file changed, 29 insertions(+), 77 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 3d7129b04..53c0180c7 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -86,60 +86,26 @@ class RedBullTVIE(InfoExtractor): }] def _real_extract(self, url): - # video_id is 'AP-...' ID - video_id = self._match_id(url) + # we want to use the rrn ID as the video id, but we can't know it until + # after downloading the webpage + webpage = self._download_webpage(url, video_id=url) - # Try downloading the webpage multiple times in order to get a repsonse - # cache which will contain the result of a query to - # 'https://www.redbull.com/v3/api/composition/v3/query/en-INT?rb3Schema=v1:pageConfig&filter[uriSlug]=%s' % video_id - # We use the response cache to get the rrn ID and other metadata. We do - # this instead of simply querying the API in order to preserve the - # provided URL's locale. (Annoyingly, the locale in the input URL - # ('en-us', for example) is of a different format than the locale - # required for the API request.) - tries = 3 - for i in range(tries): - try: - if i == 0: - webpage = self._download_webpage(url, video_id) - else: - webpage = self._download_webpage( - url, video_id, note='Redownloading webpage') - # extract response cache - response_cache = json.loads(self._html_search_regex( - r'', - webpage, 'response-cache')) - except RegexNotFoundError: - if i < tries - 1: - self.to_screen('Waiting before redownloading webpage') - time.sleep(2) - continue - else: - self.to_screen('Failed to download/locate response cache. Wait a few seconds and try running the command again.') - raise - break - - # select the key that includes the string 'pageConfig' - metadata = json.loads( - response_cache[ - [key for key in response_cache.keys() if 'pageConfig' in key][0] - ]['response'] - )['data'] - - # extract rrn ID - rrn_id_ext = metadata['analytics']['asset']['trackingDimensions']['masterID'] - # trim locale from the end of rrn_id_ext - rrn_id = ':'.join(rrn_id_ext.split(':')[:-1]) + info = json.loads(self._html_search_regex( + r'', webpage, 'video info')) + rrn_id = self._search_regex( + r'(rrn:.*):', + try_get(info, lambda x: x['associatedMedia']['embedUrl'], compat_str) + or try_get(info, lambda x: x["embedUrl"], compat_str), + 'rrn ID') # get access token for download session = self._download_json( - 'https://api.redbull.tv/v3/session', video_id, + 'https://api.redbull.tv/v3/session', rrn_id, note='Downloading access token', query={ - 'category': 'personal_computer', 'os_family': 'http', }) if session.get('code') == 'error': - raise ExtractorError('%s said: %s' % ( + raise ExtractorError('{0} said: {1}'.format( self.IE_NAME, session['message'])) token = session['token'] @@ -147,55 +113,41 @@ class RedBullTVIE(InfoExtractor): # subtitle tracks are also listed in this m3u8, but yt-dl does not # currently implement an easy way to download m3u8 VTT subtitles formats = self._extract_m3u8_formats( - 'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (rrn_id, token), - video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls') + 'https://dms.redbull.tv/v3/{0}/{1}/playlist.m3u8'.format(rrn_id, token), + rrn_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls') self._sort_formats(formats) # download more metadata - metadata2 = self._download_json( - 'https://api.redbull.tv/v3/products/%s' % rrn_id, - video_id, note='Downloading video information', + metadata = self._download_json( + 'https://api.redbull.tv/v3/products/{0}'.format(rrn_id), + rrn_id, note='Downloading video information', headers={'Authorization': token} ) # extract metadata - title = try_get(metadata2, lambda x: x['title'], compat_str) or \ + title = try_get(metadata, lambda x: x['title'], compat_str) or \ try_get(metadata, lambda x: x['analytics']['asset']['title'], compat_str) - subheading = try_get(metadata2, lambda x: x['subheading'], compat_str) + subheading = try_get(metadata, lambda x: x['subheading'], compat_str) if subheading: - title += ' - %s' % subheading + title += ' - {0}'.format(subheading) - long_description = try_get(metadata2, lambda x: x['long_description'], compat_str) + long_description = try_get( + metadata, lambda x: x['long_description'], compat_str) short_description = try_get( - metadata2, lambda x: x['short_description'], compat_str) or \ - try_get( - metadata, lambda x: x['pageMeta']['og:description'], compat_str) + metadata, lambda x: x['short_description'], compat_str) - duration = float_or_none( - try_get(metadata2, lambda x: x['duration'], int), scale=1000) + duration = float_or_none(try_get( + metadata, lambda x: x['duration'], int), scale=1000) - release_dates = [try_get( - metadata, - lambda x: x['analytics']['asset']['publishDate'], - compat_str)] - release_dates.append(try_get( - metadata, - lambda x: x['analytics']['asset']['trackingDimensions']['originalPublishingDate'], - compat_str)) - release_dates.append(try_get( - metadata, - lambda x: x['analytics']['asset']['trackingDimensions']['publishingDate'], - compat_str)) - - release_date = unified_strdate( - release_dates[0] or release_dates[1] or release_dates[2]) + date_pub = try_get(info, lambda x: x['datePublished'], compat_str) + date_create = try_get(info, lambda x: x['dateCreated'], compat_str) return { - 'id': video_id, + 'id': rrn_id, 'title': title, 'description': long_description or short_description, 'duration': duration, - 'release_date': release_date, + 'release_date': unified_strdate(date_pub or date_create), 'formats': formats, } From 05766ca4bcdbd9d112ff13fd2a70555479dd425d Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Mon, 27 Jul 2020 04:04:01 -0700 Subject: [PATCH 17/18] [redbulltv] Replace 404-ing tests and support non-AP URLs Supports slugs in the format mentioned by @Boris-de in #22037. --- youtube_dl/extractor/redbulltv.py | 78 ++++++++++--------------------- 1 file changed, 25 insertions(+), 53 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 53c0180c7..27b770693 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -18,71 +18,43 @@ class RedBullTVIE(InfoExtractor): _VALID_URL = r"""(?x)^ https?:// (?:www\.)?redbull\.com/ - [^/]+/ # locale/language code - (?:videos|recap-videos|events|episodes|films)/ - (?PAP-\w{13})(?:/live/AP-\w{13})? - (?:\?playlist)?(?:\?playlistId=rrn:content:collections:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}:[\w-]+)? + [^/]+/ # locale/language code + (?:videos|films|episodes)/ + .+ $""" _TESTS = [{ - # videos - 'url': 'https://www.redbull.com/int-en/videos/AP-1YM911N612111', - 'md5': 'e2d92baecce184ecd521fa3d72f36aa8', + 'url': 'https://www.redbull.com/int-en/videos/pedro-barros-concrete-dreams-story-clip', + 'md5': '8e265c207aa7f191dc49505ae599f3ee', 'info_dict': { - 'id': 'AP-1YM911N612111', + 'id': 'rrn:content:videos:9f804d87-bee9-48fa-aba2-a990c95f9316', 'ext': 'mp4', - 'title': 'md5:fa027630eb511593fe91e4323762e95d', - 'description': 'md5:7f769874c63e45f9b6f43315a99094c7', - 'duration': 255.0, - 'release_date': '20190809', + 'title': 'Concrete Dreams - Pedro\'s Niemeyer tour of Brazil', + 'description': 'Pedro Barros had a dream to skate the iconic works of Oscar Niemeyer – one of the pillars of modern world architecture.', + 'duration': 943.0, + 'release_date': '20200618', }, }, { - # recap-videos - 'url': 'https://www.redbull.com/int-en/recap-videos/AP-1YM8YXTC52111?playlistId=rrn:content:collections:e916768e-7b47-413d-a254-bc97d7f808f7:en-INT', - 'md5': 'aa7c6ab92ea6103f61d5fc5cbb85fd53', - 'info_dict': { - 'id': 'AP-1YM8YXTC52111', - 'ext': 'mp4', - 'title': 'md5:dc9aec63e687a534a6bb13adbb86571c', - 'description': 'md5:3774af48bf6fbc5fb6c8ebad6891f728', - 'duration': 1560.0, - 'release_date': '20190808', - }, - }, { - # events - 'url': 'https://www.redbull.com/int-en/recap-videos/AP-1ZYQN7WNW2111', - 'md5': '0f2043deef92405249c8ca96ba197901', - 'info_dict': { - 'id': 'AP-1ZYQN7WNW2111', - 'ext': 'mp4', - 'title': 'md5:c2a490a9db25823c2c9790093e3563ab', - 'description': 'md5:fb7e7a8cfaa72f7dc139238186d69800', - 'duration': 933.0, - 'release_date': '20190727', - }, - }, { - # episodes - 'url': 'https://www.redbull.com/int-en/episodes/AP-1PMHKJFCW1W11', - 'md5': 'db8271a7200d40053a1809ed0dd574ff', - 'info_dict': { - 'id': 'AP-1PMHKJFCW1W11', - 'ext': 'mp4', - 'title': 'md5:f767c9809c12c3411632cb7de9d30608', - 'description': 'md5:b5f522b89b72e1e23216e5018810bb25', - 'duration': 904.0, - 'release_date': '20170221', - }, - }, { - # films - 'url': 'https://www.redbull.com/int-en/films/AP-1ZSMAW8FH2111', + 'url': 'https://www.redbull.com/int-en/films/against-the-odds', 'md5': '3a753f7c3c1f9966ae660e05c3c7862b', 'info_dict': { - 'id': 'AP-1ZSMAW8FH2111', + 'id': 'rrn:content:films:0825a746-5930-539d-a1dd-d06a7d94ae18', 'ext': 'mp4', - 'title': 'md5:47478de1e62dadcda748c2b58ae7e343', - 'description': 'md5:9a885f6f5344b98c684f8aaf6bdfbc38', + 'title': 'Against the Odds - The story of a new team coming together', + 'description': 'In 2018, OG were at rock-bottom, hit by painful departures and scrapping in qualifiers for Dota 2\'s TI8. Against the Odds is the fairytale of this broken team\'s shot at Gaming’s biggest prize.', 'duration': 4837.0, 'release_date': '20190801', }, + }, { + 'url': 'https://www.redbull.com/int-en/episodes/red-bull-moto-spy-s4-e1', + 'md5': '4bee7de8c8caba977f22055d8d32473d', + 'info_dict': { + 'id': 'rrn:content:episode-videos:0f020f53-c089-460e-9740-cfb0b9144cda', + 'ext': 'mp4', + 'title': 'Great characters make great riders - Red Bull Moto Spy S4E1', + 'description': 'The 2020 AMA Supercross season features some talented big-name riders. Are the likes of Cooper Webb, Ken Roczen and half a dozen others destined to dominate – and block the rookies from succeeding?', + 'duration': 1448.0, + 'release_date': '20200102', + }, }] def _real_extract(self, url): From 76590695f6e79680be19be656a971d34cf7f53da Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Mon, 27 Jul 2020 04:27:19 -0700 Subject: [PATCH 18/18] [redbulltv] Remove unused imports --- youtube_dl/extractor/redbulltv.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/youtube_dl/extractor/redbulltv.py b/youtube_dl/extractor/redbulltv.py index 27b770693..58a86ecd2 100644 --- a/youtube_dl/extractor/redbulltv.py +++ b/youtube_dl/extractor/redbulltv.py @@ -4,14 +4,12 @@ from __future__ import unicode_literals from .common import InfoExtractor from ..compat import compat_str from ..utils import ( - RegexNotFoundError, ExtractorError, try_get, float_or_none, unified_strdate, ) import json -import time class RedBullTVIE(InfoExtractor):