aboutsummaryrefslogtreecommitdiffstats
path: root/youtube_dlc
diff options
context:
space:
mode:
Diffstat (limited to 'youtube_dlc')
-rw-r--r--youtube_dlc/YoutubeDL.py24
-rw-r--r--youtube_dlc/__init__.py1
-rw-r--r--youtube_dlc/extractor/extractors.py2
-rw-r--r--youtube_dlc/extractor/gdcvault.py73
-rw-r--r--youtube_dlc/extractor/googledrive.py27
-rw-r--r--youtube_dlc/extractor/kakao.py35
-rw-r--r--youtube_dlc/extractor/ndr.py15
-rw-r--r--youtube_dlc/extractor/redbulltv.py125
-rw-r--r--youtube_dlc/extractor/rtlnl.py14
-rw-r--r--youtube_dlc/extractor/soundcloud.py2
-rw-r--r--youtube_dlc/extractor/srgssr.py8
-rw-r--r--youtube_dlc/extractor/svt.py4
-rw-r--r--youtube_dlc/extractor/youtube.py24
-rw-r--r--youtube_dlc/postprocessor/embedthumbnail.py53
-rw-r--r--youtube_dlc/version.py2
15 files changed, 277 insertions, 132 deletions
diff --git a/youtube_dlc/YoutubeDL.py b/youtube_dlc/YoutubeDL.py
index 70aa4a75a..1e2070f8c 100644
--- a/youtube_dlc/YoutubeDL.py
+++ b/youtube_dlc/YoutubeDL.py
@@ -105,6 +105,7 @@ from .postprocessor import (
FFmpegFixupStretchedPP,
FFmpegMergerPP,
FFmpegPostProcessor,
+ FFmpegSubtitlesConvertorPP,
get_postprocessor,
)
from .version import __version__
@@ -1860,6 +1861,29 @@ class YoutubeDL(object):
(sub_lang, error_to_compat_str(err)))
continue
+ if self.params.get('skip_download', False):
+ if self.params.get('convertsubtitles', False):
+ subconv = FFmpegSubtitlesConvertorPP(self, format=self.params.get('convertsubtitles'))
+ filename_real_ext = os.path.splitext(filename)[1][1:]
+ filename_wo_ext = (
+ os.path.splitext(filename)[0]
+ if filename_real_ext == info_dict['ext']
+ else filename)
+ afilename = '%s.%s' % (filename_wo_ext, self.params.get('convertsubtitles'))
+ if subconv.available:
+ info_dict.setdefault('__postprocessors', [])
+ # info_dict['__postprocessors'].append(subconv)
+ if os.path.exists(encodeFilename(afilename)):
+ self.to_screen(
+ '[download] %s has already been downloaded and '
+ 'converted' % afilename)
+ else:
+ try:
+ self.post_process(filename, info_dict)
+ except (PostProcessingError) as err:
+ self.report_error('postprocessing: %s' % str(err))
+ return
+
if self.params.get('writeinfojson', False):
infofn = replace_extension(filename, 'info.json', info_dict.get('ext'))
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(infofn)):
diff --git a/youtube_dlc/__init__.py b/youtube_dlc/__init__.py
index a663417da..fc11642b9 100644
--- a/youtube_dlc/__init__.py
+++ b/youtube_dlc/__init__.py
@@ -315,6 +315,7 @@ def _real_main(argv=None):
else match_filter_func(opts.match_filter))
ydl_opts = {
+ 'convertsubtitles': opts.convertsubtitles,
'usenetrc': opts.usenetrc,
'username': opts.username,
'password': opts.password,
diff --git a/youtube_dlc/extractor/extractors.py b/youtube_dlc/extractor/extractors.py
index af1bc6e31..b971ace0a 100644
--- a/youtube_dlc/extractor/extractors.py
+++ b/youtube_dlc/extractor/extractors.py
@@ -935,7 +935,9 @@ from .rbmaradio import RBMARadioIE
from .rds import RDSIE
from .redbulltv import (
RedBullTVIE,
+ RedBullEmbedIE,
RedBullTVRrnContentIE,
+ RedBullIE,
)
from .reddit import (
RedditIE,
diff --git a/youtube_dlc/extractor/gdcvault.py b/youtube_dlc/extractor/gdcvault.py
index 2f555c1d4..a248a170d 100644
--- a/youtube_dlc/extractor/gdcvault.py
+++ b/youtube_dlc/extractor/gdcvault.py
@@ -5,9 +5,7 @@ import re
from .common import InfoExtractor
from .kaltura import KalturaIE
from ..utils import (
- HEADRequest,
sanitized_Request,
- smuggle_url,
urlencode_postdata,
)
@@ -122,67 +120,38 @@ class GDCVaultIE(InfoExtractor):
request = sanitized_Request(login_url, urlencode_postdata(login_form))
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
self._download_webpage(request, display_id, 'Logging in')
- start_page = self._download_webpage(webpage_url, display_id, 'Getting authenticated video page')
+ webpage = self._download_webpage(webpage_url, display_id, 'Getting authenticated video page')
self._download_webpage(logout_url, display_id, 'Logging out')
- return start_page
+ return webpage
def _real_extract(self, url):
video_id, name = re.match(self._VALID_URL, url).groups()
display_id = name or video_id
- webpage_url = 'http://www.gdcvault.com/play/' + video_id
- start_page = self._download_webpage(webpage_url, display_id)
-
- direct_url = self._search_regex(
- r's1\.addVariable\("file",\s*encodeURIComponent\("(/[^"]+)"\)\);',
- start_page, 'url', default=None)
- if direct_url:
- title = self._html_search_regex(
- r'<td><strong>Session Name:?</strong></td>\s*<td>(.*?)</td>',
- start_page, 'title')
- video_url = 'http://www.gdcvault.com' + direct_url
- # resolve the url so that we can detect the correct extension
- video_url = self._request_webpage(
- HEADRequest(video_url), video_id).geturl()
-
- return {
- 'id': video_id,
- 'display_id': display_id,
- 'url': video_url,
- 'title': title,
- }
+ webpage = self._download_webpage(url, display_id)
+
+ title = self._html_search_regex(
+ r'<td><strong>Session Name:?</strong></td>\s*<td>(.*?)</td>',
+ webpage, 'title')
+
+ PLAYER_REGEX = r'<iframe src=\"(?P<manifest_url>.*?)\".*?</iframe>'
+ manifest_url = self._html_search_regex(
+ PLAYER_REGEX, webpage, 'manifest_url')
+
+ partner_id = self._search_regex(
+ r'/p(?:artner_id)?/(\d+)', manifest_url, 'partner id',
+ default='1670711')
- embed_url = KalturaIE._extract_url(start_page)
- if embed_url:
- embed_url = smuggle_url(embed_url, {'source_url': url})
- ie_key = 'Kaltura'
- else:
- PLAYER_REGEX = r'<iframe src="(?P<xml_root>.+?)/(?:gdc-)?player.*?\.html.*?".*?</iframe>'
-
- xml_root = self._html_search_regex(
- PLAYER_REGEX, start_page, 'xml root', default=None)
- if xml_root is None:
- # Probably need to authenticate
- login_res = self._login(webpage_url, display_id)
- if login_res is None:
- self.report_warning('Could not login.')
- else:
- start_page = login_res
- # Grab the url from the authenticated page
- xml_root = self._html_search_regex(
- PLAYER_REGEX, start_page, 'xml root')
-
- xml_name = self._html_search_regex(
- r'<iframe src=".*?\?xml(?:=|URL=xml/)(.+?\.xml).*?".*?</iframe>',
- start_page, 'xml filename')
- embed_url = '%s/xml/%s' % (xml_root, xml_name)
- ie_key = 'DigitallySpeaking'
+ kaltura_id = self._search_regex(
+ r'entry_id=(?P<id>(?:[^&])+)', manifest_url,
+ 'kaltura id', group='id')
return {
'_type': 'url_transparent',
+ 'url': 'kaltura:%s:%s' % (partner_id, kaltura_id),
+ 'ie_key': KalturaIE.ie_key(),
'id': video_id,
'display_id': display_id,
- 'url': embed_url,
- 'ie_key': ie_key,
+ 'title': title,
}
diff --git a/youtube_dlc/extractor/googledrive.py b/youtube_dlc/extractor/googledrive.py
index 886fdd532..ec0d58a57 100644
--- a/youtube_dlc/extractor/googledrive.py
+++ b/youtube_dlc/extractor/googledrive.py
@@ -220,19 +220,27 @@ class GoogleDriveIE(InfoExtractor):
'id': video_id,
'export': 'download',
})
- urlh = self._request_webpage(
- source_url, video_id, note='Requesting source file',
- errnote='Unable to request source file', fatal=False)
+
+ def request_source_file(source_url, kind):
+ return self._request_webpage(
+ source_url, video_id, note='Requesting %s file' % kind,
+ errnote='Unable to request %s file' % kind, fatal=False)
+ urlh = request_source_file(source_url, 'source')
if urlh:
- def add_source_format(src_url):
+ def add_source_format(urlh):
formats.append({
- 'url': src_url,
+ # Use redirect URLs as download URLs in order to calculate
+ # correct cookies in _calc_cookies.
+ # Using original URLs may result in redirect loop due to
+ # google.com's cookies mistakenly used for googleusercontent.com
+ # redirect URLs (see #23919).
+ 'url': urlh.geturl(),
'ext': determine_ext(title, 'mp4').lower(),
'format_id': 'source',
'quality': 1,
})
if urlh.headers.get('Content-Disposition'):
- add_source_format(source_url)
+ add_source_format(urlh)
else:
confirmation_webpage = self._webpage_read_content(
urlh, url, video_id, note='Downloading confirmation page',
@@ -242,9 +250,12 @@ class GoogleDriveIE(InfoExtractor):
r'confirm=([^&"\']+)', confirmation_webpage,
'confirmation code', fatal=False)
if confirm:
- add_source_format(update_url_query(source_url, {
+ confirmed_source_url = update_url_query(source_url, {
'confirm': confirm,
- }))
+ })
+ urlh = request_source_file(confirmed_source_url, 'confirmed source')
+ if urlh and urlh.headers.get('Content-Disposition'):
+ add_source_format(urlh)
if not formats:
reason = self._search_regex(
diff --git a/youtube_dlc/extractor/kakao.py b/youtube_dlc/extractor/kakao.py
index 32935bb28..fefd8a215 100644
--- a/youtube_dlc/extractor/kakao.py
+++ b/youtube_dlc/extractor/kakao.py
@@ -8,13 +8,13 @@ from ..utils import (
int_or_none,
strip_or_none,
unified_timestamp,
- update_url_query,
)
class KakaoIE(InfoExtractor):
_VALID_URL = r'https?://(?:play-)?tv\.kakao\.com/(?:channel/\d+|embed/player)/cliplink/(?P<id>\d+|[^?#&]+@my)'
- _API_BASE_TMPL = 'http://tv.kakao.com/api/v1/ft/cliplinks/%s/'
+ _API_BASE_TMPL = 'http://tv.kakao.com/api/v1/ft/playmeta/cliplink/%s/'
+ _CDN_API = 'https://tv.kakao.com/katz/v1/ft/cliplink/%s/readyNplay?'
_TESTS = [{
'url': 'http://tv.kakao.com/channel/2671005/cliplink/301965083',
@@ -45,18 +45,8 @@ class KakaoIE(InfoExtractor):
def _real_extract(self, url):
video_id = self._match_id(url)
- display_id = video_id.rstrip('@my')
api_base = self._API_BASE_TMPL % video_id
-
- player_header = {
- 'Referer': update_url_query(
- 'http://tv.kakao.com/embed/player/cliplink/%s' % video_id, {
- 'service': 'kakao_tv',
- 'autoplay': '1',
- 'profile': 'HIGH',
- 'wmode': 'transparent',
- })
- }
+ cdn_api_base = self._CDN_API % video_id
query = {
'player': 'monet_html5',
@@ -73,17 +63,14 @@ class KakaoIE(InfoExtractor):
'videoOutputList', 'width', 'height', 'kbps', 'profile', 'label'])
}
- impress = self._download_json(
- api_base + 'impress', display_id, 'Downloading video info',
- query=query, headers=player_header)
+ api_json = self._download_json(
+ api_base, video_id, 'Downloading video info')
- clip_link = impress['clipLink']
+ clip_link = api_json['clipLink']
clip = clip_link['clip']
title = clip.get('title') or clip_link.get('displayTitle')
- query['tid'] = impress.get('tid', '')
-
formats = []
for fmt in clip.get('videoOutputList', []):
try:
@@ -94,15 +81,17 @@ class KakaoIE(InfoExtractor):
'profile': profile_name,
'fields': '-*,url',
})
+
fmt_url_json = self._download_json(
- api_base + 'raw/videolocation', display_id,
+ cdn_api_base, video_id,
'Downloading video URL for profile %s' % profile_name,
- query=query, headers=player_header, fatal=False)
+ query=query, fatal=False)
if fmt_url_json is None:
continue
- fmt_url = fmt_url_json['url']
+ fmt_vidLocation = fmt_url_json['videoLocation']
+ fmt_url = fmt_vidLocation['url']
formats.append({
'url': fmt_url,
'format_id': profile_name,
@@ -131,7 +120,7 @@ class KakaoIE(InfoExtractor):
})
return {
- 'id': display_id,
+ 'id': video_id,
'title': title,
'description': strip_or_none(clip.get('description')),
'uploader': clip_link.get('channel', {}).get('name'),
diff --git a/youtube_dlc/extractor/ndr.py b/youtube_dlc/extractor/ndr.py
index 2447c812e..f3897c71b 100644
--- a/youtube_dlc/extractor/ndr.py
+++ b/youtube_dlc/extractor/ndr.py
@@ -19,14 +19,15 @@ class NDRBaseIE(InfoExtractor):
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
display_id = next(group for group in mobj.groups() if group)
+ id = mobj.group('id')
webpage = self._download_webpage(url, display_id)
- return self._extract_embed(webpage, display_id)
+ return self._extract_embed(webpage, display_id, id)
class NDRIE(NDRBaseIE):
IE_NAME = 'ndr'
IE_DESC = 'NDR.de - Norddeutscher Rundfunk'
- _VALID_URL = r'https?://(?:www\.)?ndr\.de/(?:[^/]+/)*(?P<id>[^/?#]+),[\da-z]+\.html'
+ _VALID_URL = r'https?://(?:www\.)?(?:daserste\.)?ndr\.de/(?:[^/]+/)*(?P<display_id>[^/?#]+),(?P<id>[\da-z]+)\.html'
_TESTS = [{
# httpVideo, same content id
'url': 'http://www.ndr.de/fernsehen/Party-Poette-und-Parade,hafengeburtstag988.html',
@@ -86,12 +87,14 @@ class NDRIE(NDRBaseIE):
'only_matching': True,
}]
- def _extract_embed(self, webpage, display_id):
+ def _extract_embed(self, webpage, display_id, id):
embed_url = self._html_search_meta(
'embedURL', webpage, 'embed URL',
default=None) or self._search_regex(
r'\bembedUrl["\']\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage,
- 'embed URL', group='url')
+ 'embed URL', fatal=False, group='url')
+ if embed_url is None:
+ return self.url_result('ndr:%s' % id, ie=NDREmbedBaseIE.ie_key())
description = self._search_regex(
r'<p[^>]+itemprop="description">([^<]+)</p>',
webpage, 'description', default=None) or self._og_search_description(webpage)
@@ -152,7 +155,7 @@ class NJoyIE(NDRBaseIE):
'only_matching': True,
}]
- def _extract_embed(self, webpage, display_id):
+ def _extract_embed(self, webpage, display_id, id):
video_id = self._search_regex(
r'<iframe[^>]+id="pp_([\da-z]+)"', webpage, 'embed id')
description = self._search_regex(
@@ -253,7 +256,7 @@ class NDREmbedBaseIE(InfoExtractor):
class NDREmbedIE(NDREmbedBaseIE):
IE_NAME = 'ndr:embed'
- _VALID_URL = r'https?://(?:www\.)?ndr\.de/(?:[^/]+/)*(?P<id>[\da-z]+)-(?:player|externalPlayer)\.html'
+ _VALID_URL = r'https?://(?:www\.)?(?:daserste\.)?ndr\.de/(?:[^/]+/)*(?P<id>[\da-z]+)-(?:player|externalPlayer)\.html'
_TESTS = [{
'url': 'http://www.ndr.de/fernsehen/sendungen/ndr_aktuell/ndraktuell28488-player.html',
'md5': '8b9306142fe65bbdefb5ce24edb6b0a9',
diff --git a/youtube_dlc/extractor/redbulltv.py b/youtube_dlc/extractor/redbulltv.py
index dbe1aaded..3aae79f5d 100644
--- a/youtube_dlc/extractor/redbulltv.py
+++ b/youtube_dlc/extractor/redbulltv.py
@@ -1,6 +1,8 @@
# coding: utf-8
from __future__ import unicode_literals
+import re
+
from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
@@ -10,7 +12,7 @@ from ..utils import (
class RedBullTVIE(InfoExtractor):
- _VALID_URL = r'https?://(?:www\.)?redbull(?:\.tv|\.com(?:/[^/]+)?(?:/tv)?)(?:/events/[^/]+)?/(?:videos?|live)/(?P<id>AP-\w+)'
+ _VALID_URL = r'https?://(?:www\.)?redbull(?:\.tv|\.com(?:/[^/]+)?(?:/tv)?)(?:/events/[^/]+)?/(?:videos?|live|(?:film|episode)s)/(?P<id>AP-\w+)'
_TESTS = [{
# film
'url': 'https://www.redbull.tv/video/AP-1Q6XCDTAN1W11',
@@ -29,8 +31,8 @@ class RedBullTVIE(InfoExtractor):
'id': 'AP-1PMHKJFCW1W11',
'ext': 'mp4',
'title': 'Grime - Hashtags S2E4',
- 'description': 'md5:b5f522b89b72e1e23216e5018810bb25',
- 'duration': 904.6,
+ 'description': 'md5:5546aa612958c08a98faaad4abce484d',
+ 'duration': 904,
},
'params': {
'skip_download': True,
@@ -44,11 +46,15 @@ class RedBullTVIE(InfoExtractor):
}, {
'url': 'https://www.redbull.com/us-en/events/AP-1XV2K61Q51W11/live/AP-1XUJ86FDH1W11',
'only_matching': True,
+ }, {
+ 'url': 'https://www.redbull.com/int-en/films/AP-1ZSMAW8FH2111',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.redbull.com/int-en/episodes/AP-1TQWK7XE11W11',
+ 'only_matching': True,
}]
- def _real_extract(self, url):
- video_id = self._match_id(url)
-
+ def extract_info(self, video_id):
session = self._download_json(
'https://api.redbull.tv/v3/session', video_id,
note='Downloading access token', query={
@@ -105,24 +111,119 @@ class RedBullTVIE(InfoExtractor):
'subtitles': subtitles,
}
+ def _real_extract(self, url):
+ video_id = self._match_id(url)
+ return self.extract_info(video_id)
+
+
+class RedBullEmbedIE(RedBullTVIE):
+ _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})'
+ _TESTS = [{
+ # HLS manifest accessible only using assetId
+ 'url': 'https://www.redbull.com/embed/rrn:content:episode-videos:f3021f4f-3ed4-51ac-915a-11987126e405:en-INT',
+ 'only_matching': True,
+ }]
+ _VIDEO_ESSENSE_TMPL = '''... on %s {
+ videoEssence {
+ attributes
+ }
+ }'''
+
+ def _real_extract(self, url):
+ rrn_id = self._match_id(url)
+ asset_id = self._download_json(
+ 'https://edge-graphql.crepo-production.redbullaws.com/v1/graphql',
+ rrn_id, headers={'API-KEY': 'e90a1ff11335423998b100c929ecc866'},
+ query={
+ 'query': '''{
+ resource(id: "%s", enforceGeoBlocking: false) {
+ %s
+ %s
+ }
+}''' % (rrn_id, self._VIDEO_ESSENSE_TMPL % 'LiveVideo', self._VIDEO_ESSENSE_TMPL % 'VideoResource'),
+ })['data']['resource']['videoEssence']['attributes']['assetId']
+ return self.extract_info(asset_id)
+
class RedBullTVRrnContentIE(InfoExtractor):
- _VALID_URL = r'https?://(?:www\.)?redbull(?:\.tv|\.com(?:/[^/]+)?(?:/tv)?)/(?:video|live)/rrn:content:[^:]+:(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
+ _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})'
_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,
+ }, {
+ 'url': 'https://www.redbull.com/int-en/tv/film/rrn:content:films:d1f4d00e-4c04-5d19-b510-a805ffa2ab83/follow-me',
+ 'only_matching': True,
}]
def _real_extract(self, url):
- display_id = self._match_id(url)
+ region, lang, rrn_id = re.search(self._VALID_URL, url).groups()
+ rrn_id += ':%s-%s' % (lang, region.upper())
+ return self.url_result(
+ 'https://www.redbull.com/embed/' + rrn_id,
+ RedBullEmbedIE.ie_key(), rrn_id)
- webpage = self._download_webpage(url, display_id)
- video_url = self._og_search_url(webpage)
+class RedBullIE(InfoExtractor):
+ _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>[^/?#&]+)'
+ _TESTS = [{
+ 'url': 'https://www.redbull.com/int-en/episodes/grime-hashtags-s02-e04',
+ 'md5': 'db8271a7200d40053a1809ed0dd574ff',
+ 'info_dict': {
+ 'id': 'AA-1MT8DQWA91W14',
+ 'ext': 'mp4',
+ 'title': 'Grime - Hashtags S2E4',
+ 'description': 'md5:5546aa612958c08a98faaad4abce484d',
+ },
+ }, {
+ 'url': 'https://www.redbull.com/int-en/films/kilimanjaro-mountain-of-greatness',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.redbull.com/int-en/recap-videos/uci-mountain-bike-world-cup-2017-mens-xco-finals-from-vallnord',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.redbull.com/int-en/trailer-videos/kings-of-content',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.redbull.com/int-en/videos/tnts-style-red-bull-dance-your-style-s1-e12',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.redbull.com/int-en/live/mens-dh-finals-fort-william',
+ 'only_matching': True,
+ }, {
+ # only available on the int-en website so a fallback is need for the API
+ # https://www.redbull.com/v3/api/graphql/v1/v3/query/en-GB>en-INT?filter[uriSlug]=fia-wrc-saturday-recap-estonia&rb3Schema=v1:hero
+ 'url': 'https://www.redbull.com/gb-en/live/fia-wrc-saturday-recap-estonia',
+ 'only_matching': True,
+ }]
+ _INT_FALLBACK_LIST = ['de', 'en', 'es', 'fr']
+ _LAT_FALLBACK_MAP = ['ar', 'bo', 'car', 'cl', 'co', 'mx', 'pe']
+
+ def _real_extract(self, url):
+ region, lang, filter_type, display_id = re.search(self._VALID_URL, url).groups()
+ if filter_type == 'episodes':
+ filter_type = 'episode-videos'
+ elif filter_type == 'live':
+ filter_type = 'live-videos'
+
+ regions = [region.upper()]
+ if region != 'int':
+ if region in self._LAT_FALLBACK_MAP:
+ regions.append('LAT')
+ if lang in self._INT_FALLBACK_LIST:
+ regions.append('INT')
+ locale = '>'.join(['%s-%s' % (lang, reg) for reg in regions])
+
+ rrn_id = self._download_json(
+ 'https://www.redbull.com/v3/api/graphql/v1/v3/query/' + locale,
+ display_id, query={
+ 'filter[type]': filter_type,
+ 'filter[uriSlug]': display_id,
+ 'rb3Schema': 'v1:hero',
+ })['data']['id']
return self.url_result(
- video_url, ie=RedBullTVIE.ie_key(),
- video_id=RedBullTVIE._match_id(video_url))
+ 'https://www.redbull.com/embed/' + rrn_id,
+ RedBullEmbedIE.ie_key(), rrn_id)
diff --git a/youtube_dlc/extractor/rtlnl.py b/youtube_dlc/extractor/rtlnl.py
index 8be5ca236..9eaa06f25 100644
--- a/youtube_dlc/extractor/rtlnl.py
+++ b/youtube_dlc/extractor/rtlnl.py
@@ -14,13 +14,14 @@ class RtlNlIE(InfoExtractor):
_VALID_URL = r'''(?x)
https?://(?:(?:www|static)\.)?
(?:
- rtlxl\.nl/[^\#]*\#!/[^/]+/|
- rtlxl\.nl/programma/[^/]+/|
- rtl\.nl/(?:(?:system/videoplayer/(?:[^/]+/)+(?:video_)?embed\.html|embed)\b.+?\buuid=|video/)
+ rtlxl\.nl/(?:[^\#]*\#!|programma)/[^/]+/|
+ rtl\.nl/(?:(?:system/videoplayer/(?:[^/]+/)+(?:video_)?embed\.html|embed)\b.+?\buuid=|video/)|
+ embed\.rtl\.nl/\#uuid=
)
(?P<id>[0-9a-f-]+)'''
_TESTS = [{
+ # new URL schema
'url': 'https://www.rtlxl.nl/programma/rtl-nieuws/0bd1384d-d970-3086-98bb-5c104e10c26f',
'md5': '490428f1187b60d714f34e1f2e3af0b6',
'info_dict': {
@@ -33,7 +34,7 @@ class RtlNlIE(InfoExtractor):
'duration': 661.08,
},
}, {
- # old url pattern. Tests does not pass
+ # old URL schema
'url': 'http://www.rtlxl.nl/#!/rtl-nieuws-132237/82b1aad1-4a14-3d7b-b554-b0aed1b2c416',
'md5': '473d1946c1fdd050b2c0161a4b13c373',
'info_dict': {
@@ -45,6 +46,7 @@ class RtlNlIE(InfoExtractor):
'upload_date': '20160429',
'duration': 1167.96,
},
+ 'skip': '404',
}, {
# best format available a3t
'url': 'http://www.rtl.nl/system/videoplayer/derden/rtlnieuws/video_embed.html#uuid=84ae5571-ac25-4225-ae0c-ef8d9efb2aed/autoplay=false',
@@ -90,6 +92,10 @@ class RtlNlIE(InfoExtractor):
}, {
'url': 'https://static.rtl.nl/embed/?uuid=1a2970fc-5c0b-43ff-9fdc-927e39e6d1bc&autoplay=false&publicatiepunt=rtlnieuwsnl',
'only_matching': True,
+ }, {
+ # new embed URL schema
+ 'url': 'https://embed.rtl.nl/#uuid=84ae5571-ac25-4225-ae0c-ef8d9efb2aed/autoplay=false',
+ 'only_matching': True,
}]
def _real_extract(self, url):
diff --git a/youtube_dlc/extractor/soundcloud.py b/youtube_dlc/extractor/soundcloud.py
index 04b70c119..ed70b7169 100644
--- a/youtube_dlc/extractor/soundcloud.py
+++ b/youtube_dlc/extractor/soundcloud.py
@@ -649,6 +649,8 @@ class SoundcloudSetIE(SoundcloudPlaylistBaseIE):
class SoundcloudPagedPlaylistBaseIE(SoundcloudIE):
def _extract_playlist(self, base_url, playlist_id, playlist_title):
+ # Per the SoundCloud documentation, the maximum limit for a linked partioning query is 200.
+ # https://developers.soundcloud.com/blog/offset-pagination-deprecated
COMMON_QUERY = {
'limit': 200,
'linked_partitioning': '1',
diff --git a/youtube_dlc/extractor/srgssr.py b/youtube_dlc/extractor/srgssr.py
index 170dce87f..f63a1359a 100644
--- a/youtube_dlc/extractor/srgssr.py
+++ b/youtube_dlc/extractor/srgssr.py
@@ -114,7 +114,7 @@ class SRGSSRPlayIE(InfoExtractor):
[^/]+/(?P<type>video|audio)/[^?]+|
popup(?P<type_2>video|audio)player
)
- \?id=(?P<id>[0-9a-f\-]{36}|\d+)
+ \?.*?\b(?:id=|urn=urn:[^:]+:video:)(?P<id>[0-9a-f\-]{36}|\d+)
'''
_TESTS = [{
@@ -175,6 +175,12 @@ class SRGSSRPlayIE(InfoExtractor):
}, {
'url': 'https://www.srf.ch/play/tv/popupvideoplayer?id=c4dba0ca-e75b-43b2-a34f-f708a4932e01',
'only_matching': True,
+ }, {
+ 'url': 'https://www.srf.ch/play/tv/10vor10/video/snowden-beantragt-asyl-in-russland?urn=urn:srf:video:28e1a57d-5b76-4399-8ab3-9097f071e6c5',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.rts.ch/play/tv/19h30/video/le-19h30?urn=urn:rts:video:6348260',
+ 'only_matching': True,
}]
def _real_extract(self, url):
diff --git a/youtube_dlc/extractor/svt.py b/youtube_dlc/extractor/svt.py
index 8e9ec2ca3..2f6887d86 100644
--- a/youtube_dlc/extractor/svt.py
+++ b/youtube_dlc/extractor/svt.py
@@ -231,7 +231,9 @@ class SVTPlayIE(SVTPlayBaseIE):
if not svt_id:
svt_id = self._search_regex(
(r'<video[^>]+data-video-id=["\']([\da-zA-Z-]+)',
- r'"content"\s*:\s*{.*?"id"\s*:\s*"([\da-zA-Z-]+)"'),
+ r'["\']videoSvtId["\']\s*:\s*["\']([\da-zA-Z-]+)',
+ r'"content"\s*:\s*{.*?"id"\s*:\s*"([\da-zA-Z-]+)"',
+ r'["\']svtId["\']\s*:\s*["\']([\da-zA-Z-]+)'),
webpage, 'video id')
return self._extract_by_video_id(svt_id, webpage)
diff --git a/youtube_dlc/extractor/youtube.py b/youtube_dlc/extractor/youtube.py
index d3ba4c73c..1c1937721 100644
--- a/youtube_dlc/extractor/youtube.py
+++ b/youtube_dlc/extractor/youtube.py
@@ -549,7 +549,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'396': {'acodec': 'none', 'vcodec': 'av01.0.05M.08'},
'397': {'acodec': 'none', 'vcodec': 'av01.0.05M.08'},
}
- _SUBTITLE_FORMATS = ('srv1', 'srv2', 'srv3', 'ttml', 'vtt', 'json3')
+ _SUBTITLE_FORMATS = ('json3', 'srv1', 'srv2', 'srv3', 'ttml', 'vtt')
_GEO_BYPASS = False
@@ -1264,7 +1264,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'params': {
'skip_download': True,
},
- }
+ },
+ {
+ # empty description results in an empty string
+ 'url': 'https://www.youtube.com/watch?v=x41yOUIvK2k',
+ 'info_dict': {
+ 'id': 'x41yOUIvK2k',
+ 'ext': 'mp4',
+ 'title': 'IMG 3456',
+ 'description': '',
+ 'upload_date': '20170613',
+ 'uploader_id': 'ElevageOrVert',
+ 'uploader': 'ElevageOrVert',
+ },
+ 'params': {
+ 'skip_download': True,
+ },
+ },
]
def __init__(self, *args, **kwargs):
@@ -1949,7 +1965,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
''', replace_url, video_description)
video_description = clean_html(video_description)
else:
- video_description = video_details.get('shortDescription') or self._html_search_meta('description', video_webpage)
+ video_description = video_details.get('shortDescription')
+ if video_description is None:
+ video_description = self._html_search_meta('description', video_webpage)
if not smuggled_data.get('force_singlefeed', False):
if not self._downloader.params.get('noplaylist'):
diff --git a/youtube_dlc/postprocessor/embedthumbnail.py b/youtube_dlc/postprocessor/embedthumbnail.py
index e66558ea6..4a0d02fc4 100644
--- a/youtube_dlc/postprocessor/embedthumbnail.py
+++ b/youtube_dlc/postprocessor/embedthumbnail.py
@@ -13,6 +13,7 @@ from ..utils import (
encodeFilename,
PostProcessingError,
prepend_extension,
+ replace_extension,
shell_quote
)
@@ -41,27 +42,37 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
'Skipping embedding the thumbnail because the file is missing.')
return [], info
- # Check for mislabeled webp file
- with open(encodeFilename(thumbnail_filename), "rb") as f:
- b = f.read(16)
- if b'\x57\x45\x42\x50' in b: # Binary for WEBP
- [thumbnail_filename_path, thumbnail_filename_extension] = os.path.splitext(thumbnail_filename)
- if not thumbnail_filename_extension == ".webp":
- webp_thumbnail_filename = thumbnail_filename_path + ".webp"
- os.rename(encodeFilename(thumbnail_filename), encodeFilename(webp_thumbnail_filename))
- thumbnail_filename = webp_thumbnail_filename
-
- # If not a jpg or png thumbnail, convert it to jpg using ffmpeg
- if not os.path.splitext(thumbnail_filename)[1].lower() in ['.jpg', '.png']:
- jpg_thumbnail_filename = os.path.splitext(thumbnail_filename)[0] + ".jpg"
- jpg_thumbnail_filename = os.path.join(os.path.dirname(jpg_thumbnail_filename), os.path.basename(jpg_thumbnail_filename).replace('%', '_')) # ffmpeg interprets % as image sequence
-
- self._downloader.to_screen('[ffmpeg] Converting thumbnail "%s" to JPEG' % thumbnail_filename)
-
- self.run_ffmpeg(thumbnail_filename, jpg_thumbnail_filename, ['-bsf:v', 'mjpeg2jpeg'])
-
- os.remove(encodeFilename(thumbnail_filename))
- thumbnail_filename = jpg_thumbnail_filename
+ def is_webp(path):
+ with open(encodeFilename(path), 'rb') as f:
+ b = f.read(12)
+ return b[0:4] == b'RIFF' and b[8:] == b'WEBP'
+
+ # Correct extension for WebP file with wrong extension (see #25687, #25717)
+ _, thumbnail_ext = os.path.splitext(thumbnail_filename)
+ if thumbnail_ext:
+ thumbnail_ext = thumbnail_ext[1:].lower()
+ if thumbnail_ext != 'webp' and is_webp(thumbnail_filename):
+ self._downloader.to_screen(
+ '[ffmpeg] Correcting extension to webp and escaping path for thumbnail "%s"' % thumbnail_filename)
+ thumbnail_webp_filename = replace_extension(thumbnail_filename, 'webp')
+ os.rename(encodeFilename(thumbnail_filename), encodeFilename(thumbnail_webp_filename))
+ thumbnail_filename = thumbnail_webp_filename
+ thumbnail_ext = 'webp'
+
+ # Convert unsupported thumbnail formats to JPEG (see #25687, #25717)
+ if thumbnail_ext not in ['jpg', 'png']:
+ # NB: % is supposed to be escaped with %% but this does not work
+ # for input files so working around with standard substitution
+ escaped_thumbnail_filename = thumbnail_filename.replace('%', '#')
+ os.rename(encodeFilename(thumbnail_filename), encodeFilename(escaped_thumbnail_filename))
+ escaped_thumbnail_jpg_filename = replace_extension(escaped_thumbnail_filename, 'jpg')
+ self._downloader.to_screen('[ffmpeg] Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename)
+ self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_jpg_filename, ['-bsf:v', 'mjpeg2jpeg'])
+ os.remove(encodeFilename(escaped_thumbnail_filename))
+ thumbnail_jpg_filename = replace_extension(thumbnail_filename, 'jpg')
+ # Rename back to unescaped for further processing
+ os.rename(encodeFilename(escaped_thumbnail_jpg_filename), encodeFilename(thumbnail_jpg_filename))
+ thumbnail_filename = thumbnail_jpg_filename
if info['ext'] == 'mp3':
options = [
diff --git a/youtube_dlc/version.py b/youtube_dlc/version.py
index 9dd9adf08..5625b8324 100644
--- a/youtube_dlc/version.py
+++ b/youtube_dlc/version.py
@@ -1,3 +1,3 @@
from __future__ import unicode_literals
-__version__ = '2020.09.12'
+__version__ = '2020.09.14'