yt-dlp 2025.11.3.233024.dev0__py3-none-any.whl → 2025.11.11.5312.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yt_dlp/cookies.py +1 -1
- yt_dlp/downloader/external.py +7 -0
- yt_dlp/extractor/_extractors.py +5 -1
- yt_dlp/extractor/bunnycdn.py +20 -3
- yt_dlp/extractor/dplay.py +8 -7
- yt_dlp/extractor/firsttv.py +34 -1
- yt_dlp/extractor/floatplane.py +26 -27
- yt_dlp/extractor/goplay.py +7 -5
- yt_dlp/extractor/lazy_extractors.py +22 -6
- yt_dlp/extractor/mux.py +92 -0
- yt_dlp/extractor/ntvru.py +94 -57
- yt_dlp/extractor/tubetugraz.py +9 -2
- yt_dlp/extractor/twitch.py +5 -2
- yt_dlp/extractor/xhamster.py +31 -0
- yt_dlp/extractor/youtube/_base.py +1 -1
- yt_dlp/extractor/youtube/_tab.py +3 -2
- yt_dlp/extractor/youtube/_video.py +154 -30
- yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py +1 -1
- yt_dlp/networking/_curlcffi.py +4 -1
- yt_dlp/networking/_requests.py +14 -9
- yt_dlp/networking/_urllib.py +19 -1
- yt_dlp/networking/common.py +6 -2
- yt_dlp/options.py +12 -5
- yt_dlp/utils/_jsruntime.py +13 -4
- yt_dlp/version.py +3 -3
- {yt_dlp-2025.11.3.233024.dev0.data → yt_dlp-2025.11.11.5312.dev0.data}/data/share/doc/yt_dlp/README.txt +26 -15
- {yt_dlp-2025.11.3.233024.dev0.data → yt_dlp-2025.11.11.5312.dev0.data}/data/share/fish/vendor_completions.d/yt-dlp.fish +2 -2
- {yt_dlp-2025.11.3.233024.dev0.data → yt_dlp-2025.11.11.5312.dev0.data}/data/share/man/man1/yt-dlp.1 +18 -9
- {yt_dlp-2025.11.3.233024.dev0.data → yt_dlp-2025.11.11.5312.dev0.data}/data/share/zsh/site-functions/_yt-dlp +2 -2
- {yt_dlp-2025.11.3.233024.dev0.dist-info → yt_dlp-2025.11.11.5312.dev0.dist-info}/METADATA +28 -17
- {yt_dlp-2025.11.3.233024.dev0.dist-info → yt_dlp-2025.11.11.5312.dev0.dist-info}/RECORD +35 -34
- {yt_dlp-2025.11.3.233024.dev0.data → yt_dlp-2025.11.11.5312.dev0.data}/data/share/bash-completion/completions/yt-dlp +0 -0
- {yt_dlp-2025.11.3.233024.dev0.dist-info → yt_dlp-2025.11.11.5312.dev0.dist-info}/WHEEL +0 -0
- {yt_dlp-2025.11.3.233024.dev0.dist-info → yt_dlp-2025.11.11.5312.dev0.dist-info}/entry_points.txt +0 -0
- {yt_dlp-2025.11.3.233024.dev0.dist-info → yt_dlp-2025.11.11.5312.dev0.dist-info}/licenses/LICENSE +0 -0
yt_dlp/cookies.py
CHANGED
|
@@ -557,7 +557,7 @@ class WindowsChromeCookieDecryptor(ChromeCookieDecryptor):
|
|
|
557
557
|
|
|
558
558
|
|
|
559
559
|
def _extract_safari_cookies(profile, logger):
|
|
560
|
-
if sys.platform
|
|
560
|
+
if sys.platform not in ('darwin', 'ios'):
|
|
561
561
|
raise ValueError(f'unsupported platform: {sys.platform}')
|
|
562
562
|
|
|
563
563
|
if profile:
|
yt_dlp/downloader/external.py
CHANGED
|
@@ -560,6 +560,13 @@ class FFmpegFD(ExternalFD):
|
|
|
560
560
|
elif isinstance(conn, str):
|
|
561
561
|
args += ['-rtmp_conn', conn]
|
|
562
562
|
|
|
563
|
+
elif protocol == 'http_dash_segments' and info_dict.get('is_live'):
|
|
564
|
+
# ffmpeg may try to read past the latest available segments for
|
|
565
|
+
# live DASH streams unless we pass `-re`. In modern ffmpeg, this
|
|
566
|
+
# is an alias of `-readrate 1`, but `-readrate` was not added
|
|
567
|
+
# until ffmpeg 5.0, so we must stick to using `-re`
|
|
568
|
+
args += ['-re']
|
|
569
|
+
|
|
563
570
|
url = fmt['url']
|
|
564
571
|
if self.params.get('enable_file_urls') and url.startswith('file:'):
|
|
565
572
|
# The default protocol_whitelist is 'file,crypto,data' when reading local m3u8 URLs,
|
yt_dlp/extractor/_extractors.py
CHANGED
|
@@ -640,7 +640,10 @@ from .filmon import (
|
|
|
640
640
|
FilmOnIE,
|
|
641
641
|
)
|
|
642
642
|
from .filmweb import FilmwebIE
|
|
643
|
-
from .firsttv import
|
|
643
|
+
from .firsttv import (
|
|
644
|
+
FirstTVIE,
|
|
645
|
+
FirstTVLiveIE,
|
|
646
|
+
)
|
|
644
647
|
from .fivetv import FiveTVIE
|
|
645
648
|
from .flextv import FlexTVIE
|
|
646
649
|
from .flickr import FlickrIE
|
|
@@ -1197,6 +1200,7 @@ from .musicdex import (
|
|
|
1197
1200
|
MusicdexPlaylistIE,
|
|
1198
1201
|
MusicdexSongIE,
|
|
1199
1202
|
)
|
|
1203
|
+
from .mux import MuxIE
|
|
1200
1204
|
from .mx3 import (
|
|
1201
1205
|
Mx3IE,
|
|
1202
1206
|
Mx3NeoIE,
|
yt_dlp/extractor/bunnycdn.py
CHANGED
|
@@ -16,7 +16,7 @@ from ..utils.traversal import find_element, traverse_obj
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class BunnyCdnIE(InfoExtractor):
|
|
19
|
-
_VALID_URL = r'https?://(?:iframe\.mediadelivery\.net|video\.bunnycdn\.com)/(?:embed|play)/(?P<library_id>\d+)/(?P<id>[\da-f-]+)'
|
|
19
|
+
_VALID_URL = r'https?://(?:(?:iframe|player)\.mediadelivery\.net|video\.bunnycdn\.com)/(?:embed|play)/(?P<library_id>\d+)/(?P<id>[\da-f-]+)'
|
|
20
20
|
_EMBED_REGEX = [rf'<iframe[^>]+src=[\'"](?P<url>{_VALID_URL}[^\'"]*)[\'"]']
|
|
21
21
|
_TESTS = [{
|
|
22
22
|
'url': 'https://iframe.mediadelivery.net/embed/113933/e73edec1-e381-4c8b-ae73-717a140e0924',
|
|
@@ -39,7 +39,7 @@ class BunnyCdnIE(InfoExtractor):
|
|
|
39
39
|
'timestamp': 1691145748,
|
|
40
40
|
'thumbnail': r're:^https?://.*\.b-cdn\.net/32e34c4b-0d72-437c-9abb-05e67657da34/thumbnail_9172dc16\.jpg',
|
|
41
41
|
'duration': 106.0,
|
|
42
|
-
'description': 'md5:
|
|
42
|
+
'description': 'md5:11452bcb31f379ee3eaf1234d3264e44',
|
|
43
43
|
'upload_date': '20230804',
|
|
44
44
|
'title': 'Sanela ist Teil der #arbeitsmarktkraft',
|
|
45
45
|
},
|
|
@@ -58,6 +58,23 @@ class BunnyCdnIE(InfoExtractor):
|
|
|
58
58
|
'thumbnail': r're:^https?://.*\.b-cdn\.net/2e8545ec-509d-4571-b855-4cf0235ccd75/thumbnail\.jpg',
|
|
59
59
|
},
|
|
60
60
|
'params': {'skip_download': True},
|
|
61
|
+
}, {
|
|
62
|
+
# Requires any Referer
|
|
63
|
+
'url': 'https://iframe.mediadelivery.net/embed/289162/6372f5a3-68df-4ef7-a115-e1110186c477',
|
|
64
|
+
'info_dict': {
|
|
65
|
+
'id': '6372f5a3-68df-4ef7-a115-e1110186c477',
|
|
66
|
+
'ext': 'mp4',
|
|
67
|
+
'title': '12-Creating Small Asset Blockouts -Timelapse.mp4',
|
|
68
|
+
'description': '',
|
|
69
|
+
'duration': 263.0,
|
|
70
|
+
'timestamp': 1724485440,
|
|
71
|
+
'upload_date': '20240824',
|
|
72
|
+
'thumbnail': r're:^https?://.*\.b-cdn\.net/6372f5a3-68df-4ef7-a115-e1110186c477/thumbnail\.jpg',
|
|
73
|
+
},
|
|
74
|
+
'params': {'skip_download': True},
|
|
75
|
+
}, {
|
|
76
|
+
'url': 'https://player.mediadelivery.net/embed/519128/875880a9-bcc2-4038-9e05-e5024bba9b70',
|
|
77
|
+
'only_matching': True,
|
|
61
78
|
}]
|
|
62
79
|
_WEBPAGE_TESTS = [{
|
|
63
80
|
# Stream requires Referer
|
|
@@ -100,7 +117,7 @@ class BunnyCdnIE(InfoExtractor):
|
|
|
100
117
|
video_id, library_id = self._match_valid_url(url).group('id', 'library_id')
|
|
101
118
|
webpage = self._download_webpage(
|
|
102
119
|
f'https://iframe.mediadelivery.net/embed/{library_id}/{video_id}', video_id,
|
|
103
|
-
headers=
|
|
120
|
+
headers={'Referer': smuggled_data.get('Referer') or 'https://iframe.mediadelivery.net/'},
|
|
104
121
|
query=traverse_obj(parse_qs(url), {'token': 'token', 'expires': 'expires'}))
|
|
105
122
|
|
|
106
123
|
if html_title := self._html_extract_title(webpage, default=None) == '403':
|
yt_dlp/extractor/dplay.py
CHANGED
|
@@ -1063,7 +1063,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|
|
1063
1063
|
'ext': 'mp4',
|
|
1064
1064
|
'title': 'German Gold',
|
|
1065
1065
|
'description': 'md5:f3073306553a8d9b40e6ac4cdbf09fc6',
|
|
1066
|
-
'display_id': 'german-gold',
|
|
1066
|
+
'display_id': 'goldrausch-in-australien/german-gold',
|
|
1067
1067
|
'episode': 'Episode 1',
|
|
1068
1068
|
'episode_number': 1,
|
|
1069
1069
|
'season': 'Season 5',
|
|
@@ -1112,7 +1112,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|
|
1112
1112
|
'ext': 'mp4',
|
|
1113
1113
|
'title': '24 Stunden auf der Feuerwache 3',
|
|
1114
1114
|
'description': 'md5:f3084ef6170bfb79f9a6e0c030e09330',
|
|
1115
|
-
'display_id': '24-stunden-auf-der-feuerwache-3',
|
|
1115
|
+
'display_id': 'feuerwache-3-alarm-in-muenchen/24-stunden-auf-der-feuerwache-3',
|
|
1116
1116
|
'episode': 'Episode 1',
|
|
1117
1117
|
'episode_number': 1,
|
|
1118
1118
|
'season': 'Season 1',
|
|
@@ -1134,7 +1134,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|
|
1134
1134
|
'ext': 'mp4',
|
|
1135
1135
|
'title': 'Der Poltergeist im Kostümladen',
|
|
1136
1136
|
'description': 'md5:20b52b9736a0a3a7873d19a238fad7fc',
|
|
1137
|
-
'display_id': 'der-poltergeist-im-kostumladen',
|
|
1137
|
+
'display_id': 'ghost-adventures/der-poltergeist-im-kostumladen',
|
|
1138
1138
|
'episode': 'Episode 1',
|
|
1139
1139
|
'episode_number': 1,
|
|
1140
1140
|
'season': 'Season 25',
|
|
@@ -1156,7 +1156,7 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|
|
1156
1156
|
'ext': 'mp4',
|
|
1157
1157
|
'title': 'Das Geheimnis meines Bruders',
|
|
1158
1158
|
'description': 'md5:3167550bb582eb9c92875c86a0a20882',
|
|
1159
|
-
'display_id': 'das-geheimnis-meines-bruders',
|
|
1159
|
+
'display_id': 'evil-gesichter-des-boesen/das-geheimnis-meines-bruders',
|
|
1160
1160
|
'episode': 'Episode 1',
|
|
1161
1161
|
'episode_number': 1,
|
|
1162
1162
|
'season': 'Season 1',
|
|
@@ -1175,18 +1175,19 @@ class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
|
|
|
1175
1175
|
|
|
1176
1176
|
def _real_extract(self, url):
|
|
1177
1177
|
domain, programme, alternate_id = self._match_valid_url(url).groups()
|
|
1178
|
+
display_id = f'{programme}/{alternate_id}'
|
|
1178
1179
|
meta = self._download_json(
|
|
1179
1180
|
f'https://de-api.loma-cms.com/feloma/videos/{alternate_id}/',
|
|
1180
|
-
|
|
1181
|
+
display_id, query={
|
|
1181
1182
|
'environment': domain.split('.')[0],
|
|
1182
1183
|
'v': '2',
|
|
1183
1184
|
'filter[show.slug]': programme,
|
|
1184
1185
|
}, fatal=False)
|
|
1185
|
-
video_id = traverse_obj(meta, ('uid', {str}, {lambda s: s[-7:]})) or
|
|
1186
|
+
video_id = traverse_obj(meta, ('uid', {str}, {lambda s: s[-7:]})) or display_id
|
|
1186
1187
|
|
|
1187
1188
|
disco_api_info = self._get_disco_api_info(
|
|
1188
1189
|
url, video_id, 'eu1-prod.disco-api.com', domain.replace('.', ''), 'DE')
|
|
1189
|
-
disco_api_info['display_id'] =
|
|
1190
|
+
disco_api_info['display_id'] = display_id
|
|
1190
1191
|
disco_api_info['categories'] = traverse_obj(meta, (
|
|
1191
1192
|
'taxonomies', lambda _, v: v['category'] == 'genre', 'title', {str.strip}, filter, all, filter))
|
|
1192
1193
|
|
yt_dlp/extractor/firsttv.py
CHANGED
|
@@ -10,7 +10,7 @@ from ..utils import (
|
|
|
10
10
|
unified_strdate,
|
|
11
11
|
url_or_none,
|
|
12
12
|
)
|
|
13
|
-
from ..utils.traversal import traverse_obj
|
|
13
|
+
from ..utils.traversal import require, traverse_obj
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class FirstTVIE(InfoExtractor):
|
|
@@ -129,3 +129,36 @@ class FirstTVIE(InfoExtractor):
|
|
|
129
129
|
return self.playlist_result(
|
|
130
130
|
self._entries(items), display_id, self._og_search_title(webpage, default=None),
|
|
131
131
|
thumbnail=self._og_search_thumbnail(webpage, default=None))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class FirstTVLiveIE(InfoExtractor):
|
|
135
|
+
IE_NAME = '1tv:live'
|
|
136
|
+
IE_DESC = 'Первый канал (прямой эфир)'
|
|
137
|
+
_VALID_URL = r'https?://(?:www\.)?1tv\.ru/live'
|
|
138
|
+
|
|
139
|
+
_TESTS = [{
|
|
140
|
+
'url': 'https://www.1tv.ru/live',
|
|
141
|
+
'info_dict': {
|
|
142
|
+
'id': 'live',
|
|
143
|
+
'ext': 'mp4',
|
|
144
|
+
'title': r're:ПЕРВЫЙ КАНАЛ ПРЯМОЙ ЭФИР СМОТРЕТЬ ОНЛАЙН \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
|
|
145
|
+
'live_status': 'is_live',
|
|
146
|
+
},
|
|
147
|
+
'params': {'skip_download': 'livestream'},
|
|
148
|
+
}]
|
|
149
|
+
|
|
150
|
+
def _real_extract(self, url):
|
|
151
|
+
display_id = 'live'
|
|
152
|
+
webpage = self._download_webpage(url, display_id, fatal=False)
|
|
153
|
+
|
|
154
|
+
streams_list = self._download_json('https://stream.1tv.ru/api/playlist/1tvch-v1_as_array.json', display_id)
|
|
155
|
+
mpd_url = traverse_obj(streams_list, ('mpd', ..., {url_or_none}, any, {require('mpd url')}))
|
|
156
|
+
# FFmpeg needs to be passed -re to not seek past live window. This is handled by core
|
|
157
|
+
formats, _ = self._extract_mpd_formats_and_subtitles(mpd_url, display_id, mpd_id='dash')
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
'id': display_id,
|
|
161
|
+
'title': self._html_extract_title(webpage),
|
|
162
|
+
'formats': formats,
|
|
163
|
+
'is_live': True,
|
|
164
|
+
}
|
yt_dlp/extractor/floatplane.py
CHANGED
|
@@ -6,15 +6,15 @@ from ..utils import (
|
|
|
6
6
|
OnDemandPagedList,
|
|
7
7
|
clean_html,
|
|
8
8
|
determine_ext,
|
|
9
|
+
float_or_none,
|
|
9
10
|
format_field,
|
|
10
11
|
int_or_none,
|
|
11
12
|
join_nonempty,
|
|
12
|
-
parse_codecs,
|
|
13
13
|
parse_iso8601,
|
|
14
14
|
url_or_none,
|
|
15
15
|
urljoin,
|
|
16
16
|
)
|
|
17
|
-
from ..utils.traversal import traverse_obj
|
|
17
|
+
from ..utils.traversal import require, traverse_obj
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class FloatplaneBaseIE(InfoExtractor):
|
|
@@ -50,37 +50,31 @@ class FloatplaneBaseIE(InfoExtractor):
|
|
|
50
50
|
media_id = media['id']
|
|
51
51
|
media_typ = media.get('type') or 'video'
|
|
52
52
|
|
|
53
|
-
metadata = self._download_json(
|
|
54
|
-
f'{self._BASE_URL}/api/v3/content/{media_typ}', media_id, query={'id': media_id},
|
|
55
|
-
note=f'Downloading {media_typ} metadata', impersonate=self._IMPERSONATE_TARGET)
|
|
56
|
-
|
|
57
53
|
stream = self._download_json(
|
|
58
|
-
f'{self._BASE_URL}/api/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}, note=f'Downloading {media_typ} stream data',
|
|
54
|
+
f'{self._BASE_URL}/api/v3/delivery/info', media_id,
|
|
55
|
+
query={'scenario': 'onDemand', 'entityId': media_id},
|
|
56
|
+
note=f'Downloading {media_typ} stream data',
|
|
62
57
|
impersonate=self._IMPERSONATE_TARGET)
|
|
63
58
|
|
|
64
|
-
|
|
59
|
+
metadata = self._download_json(
|
|
60
|
+
f'{self._BASE_URL}/api/v3/content/{media_typ}', media_id,
|
|
61
|
+
f'Downloading {media_typ} metadata', query={'id': media_id},
|
|
62
|
+
fatal=False, impersonate=self._IMPERSONATE_TARGET)
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for i, val in (params or {}).items():
|
|
69
|
-
path = path.replace(f'{{qualityLevelParams.{i}}}', val)
|
|
70
|
-
return path
|
|
64
|
+
cdn_base_url = traverse_obj(stream, (
|
|
65
|
+
'groups', 0, 'origins', ..., 'url', {url_or_none}, any, {require('cdn base url')}))
|
|
71
66
|
|
|
72
67
|
formats = []
|
|
73
|
-
for
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
format_id = traverse_obj(quality, ('name', {str}))
|
|
68
|
+
for variant in traverse_obj(stream, ('groups', 0, 'variants', lambda _, v: v['url'])):
|
|
69
|
+
format_url = urljoin(cdn_base_url, variant['url'])
|
|
70
|
+
format_id = traverse_obj(variant, ('name', {str}))
|
|
77
71
|
hls_aes = {}
|
|
78
72
|
m3u8_data = None
|
|
79
73
|
|
|
80
74
|
# If we need impersonation for the API, then we need it for HLS keys too: extract in advance
|
|
81
75
|
if self._IMPERSONATE_TARGET is not None:
|
|
82
76
|
m3u8_data = self._download_webpage(
|
|
83
|
-
|
|
77
|
+
format_url, media_id, fatal=False, impersonate=self._IMPERSONATE_TARGET, headers=self._HEADERS,
|
|
84
78
|
note=join_nonempty('Downloading', format_id, 'm3u8 information', delim=' '),
|
|
85
79
|
errnote=join_nonempty('Failed to download', format_id, 'm3u8 information', delim=' '))
|
|
86
80
|
if not m3u8_data:
|
|
@@ -98,14 +92,19 @@ class FloatplaneBaseIE(InfoExtractor):
|
|
|
98
92
|
hls_aes['key'] = urlh.read().hex()
|
|
99
93
|
|
|
100
94
|
formats.append({
|
|
101
|
-
**traverse_obj(
|
|
95
|
+
**traverse_obj(variant, {
|
|
102
96
|
'format_note': ('label', {str}),
|
|
103
|
-
'width': ('width', {
|
|
104
|
-
'height': ('height', {
|
|
97
|
+
'width': ('meta', 'video', 'width', {int_or_none}),
|
|
98
|
+
'height': ('meta', 'video', 'height', {int_or_none}),
|
|
99
|
+
'vcodec': ('meta', 'video', 'codec', {str}),
|
|
100
|
+
'acodec': ('meta', 'audio', 'codec', {str}),
|
|
101
|
+
'vbr': ('meta', 'video', 'bitrate', 'average', {int_or_none(scale=1000)}),
|
|
102
|
+
'abr': ('meta', 'audio', 'bitrate', 'average', {int_or_none(scale=1000)}),
|
|
103
|
+
'audio_channels': ('meta', 'audio', 'channelCount', {int_or_none}),
|
|
104
|
+
'fps': ('meta', 'video', 'fps', {float_or_none}),
|
|
105
105
|
}),
|
|
106
|
-
|
|
107
|
-
'
|
|
108
|
-
'ext': determine_ext(url.partition('/chunk.m3u8')[0], 'mp4'),
|
|
106
|
+
'url': format_url,
|
|
107
|
+
'ext': determine_ext(format_url.partition('/chunk.m3u8')[0], 'mp4'),
|
|
109
108
|
'format_id': format_id,
|
|
110
109
|
'hls_media_playlist_data': m3u8_data,
|
|
111
110
|
'hls_aes': hls_aes or None,
|
yt_dlp/extractor/goplay.py
CHANGED
|
@@ -13,12 +13,14 @@ from ..utils.traversal import get_first, traverse_obj
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class GoPlayIE(InfoExtractor):
|
|
16
|
-
|
|
16
|
+
IE_NAME = 'play.tv'
|
|
17
|
+
IE_DESC = 'PLAY (formerly goplay.be)'
|
|
18
|
+
_VALID_URL = r'https?://(www\.)?play\.tv/video/([^/?#]+/[^/?#]+/|)(?P<id>[^/#]+)'
|
|
17
19
|
|
|
18
20
|
_NETRC_MACHINE = 'goplay'
|
|
19
21
|
|
|
20
22
|
_TESTS = [{
|
|
21
|
-
'url': 'https://www.
|
|
23
|
+
'url': 'https://www.play.tv/video/de-slimste-mens-ter-wereld/de-slimste-mens-ter-wereld-s22/de-slimste-mens-ter-wereld-s22-aflevering-1',
|
|
22
24
|
'info_dict': {
|
|
23
25
|
'id': '2baa4560-87a0-421b-bffc-359914e3c387',
|
|
24
26
|
'ext': 'mp4',
|
|
@@ -33,7 +35,7 @@ class GoPlayIE(InfoExtractor):
|
|
|
33
35
|
'params': {'skip_download': True},
|
|
34
36
|
'skip': 'This video is only available for registered users',
|
|
35
37
|
}, {
|
|
36
|
-
'url': 'https://www.
|
|
38
|
+
'url': 'https://www.play.tv/video/1917',
|
|
37
39
|
'info_dict': {
|
|
38
40
|
'id': '40cac41d-8d29-4ef5-aa11-75047b9f0907',
|
|
39
41
|
'ext': 'mp4',
|
|
@@ -43,7 +45,7 @@ class GoPlayIE(InfoExtractor):
|
|
|
43
45
|
'params': {'skip_download': True},
|
|
44
46
|
'skip': 'This video is only available for registered users',
|
|
45
47
|
}, {
|
|
46
|
-
'url': 'https://www.
|
|
48
|
+
'url': 'https://www.play.tv/video/de-mol/de-mol-s11/de-mol-s11-aflevering-1#autoplay',
|
|
47
49
|
'info_dict': {
|
|
48
50
|
'id': 'ecb79672-92b9-4cd9-a0d7-e2f0250681ee',
|
|
49
51
|
'ext': 'mp4',
|
|
@@ -101,7 +103,7 @@ class GoPlayIE(InfoExtractor):
|
|
|
101
103
|
break
|
|
102
104
|
|
|
103
105
|
api = self._download_json(
|
|
104
|
-
f'https://api.
|
|
106
|
+
f'https://api.play.tv/web/v1/videos/long-form/{video_id}',
|
|
105
107
|
video_id, headers={
|
|
106
108
|
'Authorization': f'Bearer {self._id_token}',
|
|
107
109
|
**self.geo_verification_headers(),
|