summaryrefslogtreecommitdiffstats
path: root/yt_dlp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--yt_dlp/extractor/cbc.py89
-rw-r--r--yt_dlp/extractor/dplay.py8
-rw-r--r--yt_dlp/extractor/niconico.py22
-rw-r--r--yt_dlp/extractor/olympics.py23
-rw-r--r--yt_dlp/extractor/youku.py2
-rw-r--r--yt_dlp/extractor/youtube.py11
-rw-r--r--yt_dlp/jsinterp.py6
-rw-r--r--yt_dlp/version.py6
8 files changed, 111 insertions, 56 deletions
diff --git a/yt_dlp/extractor/cbc.py b/yt_dlp/extractor/cbc.py
index 373c9d2..40224f6 100644
--- a/yt_dlp/extractor/cbc.py
+++ b/yt_dlp/extractor/cbc.py
@@ -806,11 +806,11 @@ class CBCGemLiveIE(InfoExtractor):
'title': 'Ottawa',
'description': 'The live TV channel and local programming from Ottawa',
'thumbnail': 'https://thumbnails.cbc.ca/maven_legacy/thumbnails/CBC_OTT_VMS/Live_Channel_Static_Images/Ottawa_2880x1620.jpg',
- 'is_live': True,
+ 'live_status': 'is_live',
'id': 'AyqZwxRqh8EH',
'ext': 'mp4',
- 'timestamp': 1492106160,
- 'upload_date': '20170413',
+ 'release_timestamp': 1492106160,
+ 'release_date': '20170413',
'uploader': 'CBCC-NEW',
},
'skip': 'Live might have ended',
@@ -839,49 +839,84 @@ class CBCGemLiveIE(InfoExtractor):
'description': 'March 24, 2023 | President Biden’s Ottawa visit ends with big pledges from both countries. Plus, Gwyneth Paltrow testifies in her ski collision trial.',
'live_status': 'is_live',
'thumbnail': r're:https://images.gem.cbc.ca/v1/cbc-gem/live/.*',
- 'timestamp': 1679706000,
- 'upload_date': '20230325',
+ 'release_timestamp': 1679706000,
+ 'release_date': '20230325',
},
'params': {'skip_download': True},
'skip': 'Live might have ended',
},
+ { # event replay (medianetlive)
+ 'url': 'https://gem.cbc.ca/live-event/42314',
+ 'md5': '297a9600f554f2258aed01514226a697',
+ 'info_dict': {
+ 'id': '42314',
+ 'ext': 'mp4',
+ 'live_status': 'was_live',
+ 'title': 'Women\'s Soccer - Canada vs New Zealand',
+ 'description': 'md5:36200e5f1a70982277b5a6ecea86155d',
+ 'thumbnail': r're:https://.+default\.jpg',
+ 'release_timestamp': 1721917200,
+ 'release_date': '20240725',
+ },
+ 'params': {'skip_download': True},
+ 'skip': 'Replay might no longer be available',
+ },
+ { # event replay (medianetlive)
+ 'url': 'https://gem.cbc.ca/live-event/43273',
+ 'only_matching': True,
+ },
]
+ _GEO_COUNTRIES = ['CA']
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
video_info = self._search_nextjs_data(webpage, video_id)['props']['pageProps']['data']
- # Two types of metadata JSON
+ # Three types of video_info JSON: info in root, freeTv stream/item, event replay
if not video_info.get('formattedIdMedia'):
- video_info = traverse_obj(
- video_info, (('freeTv', ('streams', ...)), 'items', lambda _, v: v['key'] == video_id, {dict}),
- get_all=False, default={})
+ if traverse_obj(video_info, ('event', 'key')) == video_id:
+ video_info = video_info['event']
+ else:
+ video_info = traverse_obj(video_info, (
+ ('freeTv', ('streams', ...)), 'items',
+ lambda _, v: v['key'].partition('-')[0] == video_id, any)) or {}
video_stream_id = video_info.get('formattedIdMedia')
if not video_stream_id:
- raise ExtractorError('Couldn\'t find video metadata, maybe this livestream is now offline', expected=True)
-
- stream_data = self._download_json(
- 'https://services.radio-canada.ca/media/validation/v2/', video_id, query={
- 'appCode': 'mpx',
- 'connectionType': 'hd',
- 'deviceType': 'ipad',
- 'idMedia': video_stream_id,
- 'multibitrate': 'true',
- 'output': 'json',
- 'tech': 'hls',
- 'manifestType': 'desktop',
- })
+ raise ExtractorError(
+ 'Couldn\'t find video metadata, maybe this livestream is now offline', expected=True)
+
+ live_status = 'was_live' if video_info.get('isVodEnabled') else 'is_live'
+ release_timestamp = traverse_obj(video_info, ('airDate', {parse_iso8601}))
+
+ if live_status == 'is_live' and release_timestamp and release_timestamp > time.time():
+ formats = []
+ live_status = 'is_upcoming'
+ self.raise_no_formats('This livestream has not yet started', expected=True)
+ else:
+ stream_data = self._download_json(
+ 'https://services.radio-canada.ca/media/validation/v2/', video_id, query={
+ 'appCode': 'medianetlive',
+ 'connectionType': 'hd',
+ 'deviceType': 'ipad',
+ 'idMedia': video_stream_id,
+ 'multibitrate': 'true',
+ 'output': 'json',
+ 'tech': 'hls',
+ 'manifestType': 'desktop',
+ })
+ formats = self._extract_m3u8_formats(
+ stream_data['url'], video_id, 'mp4', live=live_status == 'is_live')
return {
'id': video_id,
- 'formats': self._extract_m3u8_formats(stream_data['url'], video_id, 'mp4', live=True),
- 'is_live': True,
+ 'formats': formats,
+ 'live_status': live_status,
+ 'release_timestamp': release_timestamp,
**traverse_obj(video_info, {
- 'title': 'title',
- 'description': 'description',
+ 'title': ('title', {str}),
+ 'description': ('description', {str}),
'thumbnail': ('images', 'card', 'url'),
- 'timestamp': ('airDate', {parse_iso8601}),
}),
}
diff --git a/yt_dlp/extractor/dplay.py b/yt_dlp/extractor/dplay.py
index cdf84c5..8d77072 100644
--- a/yt_dlp/extractor/dplay.py
+++ b/yt_dlp/extractor/dplay.py
@@ -1147,13 +1147,19 @@ class DiscoveryPlusShowBaseIE(DPlayBaseIE):
class DiscoveryPlusItalyIE(DiscoveryPlusBaseIE):
- _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/it/video' + DPlayBaseIE._PATH_REGEX
+ _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/it/video(?:/sport|/olympics)?' + DPlayBaseIE._PATH_REGEX
_TESTS = [{
'url': 'https://www.discoveryplus.com/it/video/i-signori-della-neve/stagione-2-episodio-1-i-preparativi',
'only_matching': True,
}, {
'url': 'https://www.discoveryplus.com/it/video/super-benny/trailer',
'only_matching': True,
+ }, {
+ 'url': 'https://www.discoveryplus.com/it/video/olympics/dplus-sport-dplus-sport-sport/water-polo-greece-italy',
+ 'only_matching': True,
+ }, {
+ 'url': 'https://www.discoveryplus.com/it/video/sport/dplus-sport-dplus-sport-sport/lisa-vittozzi-allinferno-e-ritorno',
+ 'only_matching': True,
}]
_PRODUCT = 'dplus_it'
diff --git a/yt_dlp/extractor/niconico.py b/yt_dlp/extractor/niconico.py
index 9d7b010..179e7a9 100644
--- a/yt_dlp/extractor/niconico.py
+++ b/yt_dlp/extractor/niconico.py
@@ -40,7 +40,6 @@ class NiconicoIE(InfoExtractor):
_TESTS = [{
'url': 'http://www.nicovideo.jp/watch/sm22312215',
- 'md5': 'd1a75c0823e2f629128c43e1212760f9',
'info_dict': {
'id': 'sm22312215',
'ext': 'mp4',
@@ -56,8 +55,8 @@ class NiconicoIE(InfoExtractor):
'comment_count': int,
'genres': ['未設定'],
'tags': [],
- 'expected_protocol': str,
},
+ 'params': {'skip_download': 'm3u8'},
}, {
# File downloaded with and without credentials are different, so omit
# the md5 field
@@ -77,8 +76,8 @@ class NiconicoIE(InfoExtractor):
'view_count': int,
'genres': ['音楽・サウンド'],
'tags': ['Translation_Request', 'Kagamine_Rin', 'Rin_Original'],
- 'expected_protocol': str,
},
+ 'params': {'skip_download': 'm3u8'},
}, {
# 'video exists but is marked as "deleted"
# md5 is unstable
@@ -112,7 +111,6 @@ class NiconicoIE(InfoExtractor):
}, {
# video not available via `getflv`; "old" HTML5 video
'url': 'http://www.nicovideo.jp/watch/sm1151009',
- 'md5': 'f95a3d259172667b293530cc2e41ebda',
'info_dict': {
'id': 'sm1151009',
'ext': 'mp4',
@@ -128,11 +126,10 @@ class NiconicoIE(InfoExtractor):
'comment_count': int,
'genres': ['ゲーム'],
'tags': [],
- 'expected_protocol': str,
},
+ 'params': {'skip_download': 'm3u8'},
}, {
# "New" HTML5 video
- # md5 is unstable
'url': 'http://www.nicovideo.jp/watch/sm31464864',
'info_dict': {
'id': 'sm31464864',
@@ -149,12 +146,11 @@ class NiconicoIE(InfoExtractor):
'comment_count': int,
'genres': ['アニメ'],
'tags': [],
- 'expected_protocol': str,
},
+ 'params': {'skip_download': 'm3u8'},
}, {
# Video without owner
'url': 'http://www.nicovideo.jp/watch/sm18238488',
- 'md5': 'd265680a1f92bdcbbd2a507fc9e78a9e',
'info_dict': {
'id': 'sm18238488',
'ext': 'mp4',
@@ -168,8 +164,8 @@ class NiconicoIE(InfoExtractor):
'comment_count': int,
'genres': ['エンターテイメント'],
'tags': [],
- 'expected_protocol': str,
},
+ 'params': {'skip_download': 'm3u8'},
}, {
'url': 'http://sp.nicovideo.jp/watch/sm28964488?ss_pos=1&cp_in=wt_tg',
'only_matching': True,
@@ -458,9 +454,11 @@ class NiconicoIE(InfoExtractor):
if video_id.startswith('so'):
video_id = self._match_id(handle.url)
- api_data = self._parse_json(self._html_search_regex(
- 'data-api-data="([^"]+)"', webpage,
- 'API data', default='{}'), video_id)
+ api_data = traverse_obj(
+ self._parse_json(self._html_search_meta('server-response', webpage) or '', video_id),
+ ('data', 'response', {dict}))
+ if not api_data:
+ raise ExtractorError('Server response data not found')
except ExtractorError as e:
try:
api_data = self._download_json(
diff --git a/yt_dlp/extractor/olympics.py b/yt_dlp/extractor/olympics.py
index a50c510..bbf83e5 100644
--- a/yt_dlp/extractor/olympics.py
+++ b/yt_dlp/extractor/olympics.py
@@ -4,7 +4,9 @@ from ..utils import (
ExtractorError,
int_or_none,
parse_iso8601,
+ parse_qs,
try_get,
+ update_url,
url_or_none,
)
from ..utils.traversal import traverse_obj
@@ -24,9 +26,6 @@ class OlympicsReplayIE(InfoExtractor):
'thumbnail': 'https://img.olympics.com/images/image/private/t_1-1_1280/primary/nua4o7zwyaznoaejpbk2',
'duration': 7017.0,
},
- 'params': {
- 'skip_download': True,
- },
}, {
'url': 'https://olympics.com/en/original-series/episode/b-boys-and-b-girls-take-the-spotlight-breaking-life-road-to-paris-2024',
'info_dict': {
@@ -74,7 +73,7 @@ class OlympicsReplayIE(InfoExtractor):
is_live = traverse_obj(data, ('streamingStatus', {str})) == 'LIVE'
m3u8_url = traverse_obj(data, ('videoUrl', {url_or_none})) or data['streamUrl']
- tokenized_url = m3u8_url if is_live else self._tokenize_url(m3u8_url, video_id)
+ tokenized_url = self._tokenize_url(m3u8_url, data['jwtToken'], is_live, video_id)
try:
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
@@ -95,10 +94,20 @@ class OlympicsReplayIE(InfoExtractor):
}),
}
- def _tokenize_url(self, url, video_id):
+ def _tokenize_url(self, url, token, is_live, video_id):
+ return self._download_json(
+ 'https://metering.olympics.com/tokengenerator', video_id,
+ 'Downloading tokenized m3u8 url', query={
+ **parse_qs(url),
+ 'url': update_url(url, query=None),
+ 'service-id': 'live' if is_live else 'vod',
+ 'user-auth': token,
+ })['data']['url']
+
+ def _legacy_tokenize_url(self, url, video_id):
return self._download_json(
'https://olympics.com/tokenGenerator', video_id,
- 'Downloading tokenized m3u8 url', query={'url': url})
+ 'Downloading legacy tokenized m3u8 url', query={'url': url})
def _real_extract(self, url):
video_id = self._match_id(url)
@@ -130,7 +139,7 @@ class OlympicsReplayIE(InfoExtractor):
})
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
- self._tokenize_url(m3u8_url, video_uuid), video_uuid, 'mp4', m3u8_id='hls')
+ self._legacy_tokenize_url(m3u8_url, video_uuid), video_uuid, 'mp4', m3u8_id='hls')
return {
'id': video_uuid,
diff --git a/yt_dlp/extractor/youku.py b/yt_dlp/extractor/youku.py
index fa6b053..3bdfa6c 100644
--- a/yt_dlp/extractor/youku.py
+++ b/yt_dlp/extractor/youku.py
@@ -136,7 +136,7 @@ class YoukuIE(InfoExtractor):
# request basic data
basic_data_params = {
'vid': video_id,
- 'ccode': '0524',
+ 'ccode': '0564',
'client_ip': '192.168.1.1',
'utid': cna,
'client_ts': time.time() / 1000,
diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py
index 88e1a28..224c9b9 100644
--- a/yt_dlp/extractor/youtube.py
+++ b/yt_dlp/extractor/youtube.py
@@ -3180,6 +3180,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# * b=String.fromCharCode(110),c=a.get(b))&&c=narray[idx](c)
# * a.D&&(b="nn"[+a.D],c=a.get(b))&&(c=narray[idx](c),a.set(b,c),narray.length||nfunc("")
# * a.D&&(PL(a),b=a.j.n||null)&&(b=narray[0](b),a.set("n",b),narray.length||nfunc("")
+ # * a.D&&(b="nn"[+a.D],vL(a),c=a.j[b]||null)&&(c=narray[idx](c),a.set(b,c),narray.length||nfunc("")
funcname, idx = self._search_regex(
r'''(?x)
(?:
@@ -3187,7 +3188,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
(?:
b=String\.fromCharCode\(110\)|
(?P<str_idx>[a-zA-Z0-9_$.]+)&&\(b="nn"\[\+(?P=str_idx)\]
- ),c=a\.get\(b\)\)&&\(c=|
+ )
+ (?:
+ ,[a-zA-Z0-9_$]+\(a\))?,c=a\.
+ (?:
+ get\(b\)|
+ [a-zA-Z0-9_$]+\[b\]\|\|null
+ )\)&&\(c=|
\b(?P<var>[a-zA-Z0-9_$]+)=
)(?P<nfunc>[a-zA-Z0-9_$]+)(?:\[(?P<idx>\d+)\])?\([a-zA-Z]\)
(?(var),[a-zA-Z0-9_$]+\.set\("n"\,(?P=var)\),(?P=nfunc)\.length)''',
@@ -3737,7 +3744,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
def _get_requested_clients(self, url, smuggled_data):
requested_clients = []
broken_clients = []
- default = ['ios', 'tv']
+ default = ['ios', 'web_creator']
allowed_clients = sorted(
(client for client in INNERTUBE_CLIENTS if client[:1] != '_'),
key=lambda client: INNERTUBE_CLIENTS[client]['priority'], reverse=True)
diff --git a/yt_dlp/jsinterp.py b/yt_dlp/jsinterp.py
index 851d4dc..ba059ba 100644
--- a/yt_dlp/jsinterp.py
+++ b/yt_dlp/jsinterp.py
@@ -709,9 +709,9 @@ class JSInterpreter:
obj.reverse()
return obj
elif member == 'slice':
- assertion(isinstance(obj, list), 'must be applied on a list')
- assertion(len(argvals) == 1, 'takes exactly one argument')
- return obj[argvals[0]:]
+ assertion(isinstance(obj, (list, str)), 'must be applied on a list or string')
+ assertion(len(argvals) <= 2, 'takes between 0 and 2 arguments')
+ return obj[slice(*argvals, None)]
elif member == 'splice':
assertion(isinstance(obj, list), 'must be applied on a list')
assertion(argvals, 'takes one or more arguments')
diff --git a/yt_dlp/version.py b/yt_dlp/version.py
index 81d1c2c..6633a11 100644
--- a/yt_dlp/version.py
+++ b/yt_dlp/version.py
@@ -1,8 +1,8 @@
# Autogenerated by devscripts/update-version.py
-__version__ = '2024.08.01'
+__version__ = '2024.08.06'
-RELEASE_GIT_HEAD = 'ffd7781d6588926f820b44a34b9e6e3068fb9f97'
+RELEASE_GIT_HEAD = '4d9231208332d4c32364b8cd814bff8b20232cae'
VARIANT = None
@@ -12,4 +12,4 @@ CHANNEL = 'stable'
ORIGIN = 'yt-dlp/yt-dlp'
-_pkg_version = '2024.08.01'
+_pkg_version = '2024.08.06'