yt-dlp 2025.11.14.235840.dev0__py3-none-any.whl → 2025.11.16.232923.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/downloader/common.py +2 -1
- yt_dlp/extractor/_extractors.py +11 -1
- yt_dlp/extractor/bitmovin.py +74 -0
- yt_dlp/extractor/floatplane.py +13 -0
- yt_dlp/extractor/frontro.py +164 -0
- yt_dlp/extractor/lazy_extractors.py +67 -5
- yt_dlp/extractor/mave.py +119 -36
- yt_dlp/extractor/nowcanal.py +37 -0
- yt_dlp/extractor/soundcloud.py +7 -1
- yt_dlp/extractor/yfanefa.py +67 -0
- yt_dlp/extractor/youtube/_video.py +12 -4
- yt_dlp/networking/_urllib.py +2 -0
- yt_dlp/version.py +3 -3
- {yt_dlp-2025.11.14.235840.dev0.dist-info → yt_dlp-2025.11.16.232923.dev0.dist-info}/METADATA +1 -1
- {yt_dlp-2025.11.14.235840.dev0.dist-info → yt_dlp-2025.11.16.232923.dev0.dist-info}/RECORD +23 -19
- {yt_dlp-2025.11.14.235840.dev0.data → yt_dlp-2025.11.16.232923.dev0.data}/data/share/bash-completion/completions/yt-dlp +0 -0
- {yt_dlp-2025.11.14.235840.dev0.data → yt_dlp-2025.11.16.232923.dev0.data}/data/share/doc/yt_dlp/README.txt +0 -0
- {yt_dlp-2025.11.14.235840.dev0.data → yt_dlp-2025.11.16.232923.dev0.data}/data/share/fish/vendor_completions.d/yt-dlp.fish +0 -0
- {yt_dlp-2025.11.14.235840.dev0.data → yt_dlp-2025.11.16.232923.dev0.data}/data/share/man/man1/yt-dlp.1 +0 -0
- {yt_dlp-2025.11.14.235840.dev0.data → yt_dlp-2025.11.16.232923.dev0.data}/data/share/zsh/site-functions/_yt-dlp +0 -0
- {yt_dlp-2025.11.14.235840.dev0.dist-info → yt_dlp-2025.11.16.232923.dev0.dist-info}/WHEEL +0 -0
- {yt_dlp-2025.11.14.235840.dev0.dist-info → yt_dlp-2025.11.16.232923.dev0.dist-info}/entry_points.txt +0 -0
- {yt_dlp-2025.11.14.235840.dev0.dist-info → yt_dlp-2025.11.16.232923.dev0.dist-info}/licenses/LICENSE +0 -0
yt_dlp/downloader/common.py
CHANGED
|
@@ -461,7 +461,8 @@ class FileDownloader:
|
|
|
461
461
|
min_sleep_interval = self.params.get('sleep_interval') or 0
|
|
462
462
|
max_sleep_interval = self.params.get('max_sleep_interval') or 0
|
|
463
463
|
|
|
464
|
-
|
|
464
|
+
requested_formats = info_dict.get('requested_formats') or [info_dict]
|
|
465
|
+
if available_at := max(f.get('available_at') or 0 for f in requested_formats):
|
|
465
466
|
forced_sleep_interval = available_at - int(time.time())
|
|
466
467
|
if forced_sleep_interval > min_sleep_interval:
|
|
467
468
|
sleep_note = 'as required by the site'
|
yt_dlp/extractor/_extractors.py
CHANGED
|
@@ -268,6 +268,7 @@ from .bitchute import (
|
|
|
268
268
|
BitChuteChannelIE,
|
|
269
269
|
BitChuteIE,
|
|
270
270
|
)
|
|
271
|
+
from .bitmovin import BitmovinIE
|
|
271
272
|
from .blackboardcollaborate import (
|
|
272
273
|
BlackboardCollaborateIE,
|
|
273
274
|
BlackboardCollaborateLaunchIE,
|
|
@@ -690,6 +691,10 @@ from .frontendmasters import (
|
|
|
690
691
|
FrontendMastersIE,
|
|
691
692
|
FrontendMastersLessonIE,
|
|
692
693
|
)
|
|
694
|
+
from .frontro import (
|
|
695
|
+
TheChosenGroupIE,
|
|
696
|
+
TheChosenIE,
|
|
697
|
+
)
|
|
693
698
|
from .fujitv import FujiTVFODPlus7IE
|
|
694
699
|
from .funk import FunkIE
|
|
695
700
|
from .funker530 import Funker530IE
|
|
@@ -1093,7 +1098,10 @@ from .markiza import (
|
|
|
1093
1098
|
from .massengeschmacktv import MassengeschmackTVIE
|
|
1094
1099
|
from .masters import MastersIE
|
|
1095
1100
|
from .matchtv import MatchTVIE
|
|
1096
|
-
from .mave import
|
|
1101
|
+
from .mave import (
|
|
1102
|
+
MaveChannelIE,
|
|
1103
|
+
MaveIE,
|
|
1104
|
+
)
|
|
1097
1105
|
from .mbn import MBNIE
|
|
1098
1106
|
from .mdr import MDRIE
|
|
1099
1107
|
from .medaltv import MedalTVIE
|
|
@@ -1368,6 +1376,7 @@ from .nova import (
|
|
|
1368
1376
|
NovaIE,
|
|
1369
1377
|
)
|
|
1370
1378
|
from .novaplay import NovaPlayIE
|
|
1379
|
+
from .nowcanal import NowCanalIE
|
|
1371
1380
|
from .nowness import (
|
|
1372
1381
|
NownessIE,
|
|
1373
1382
|
NownessPlaylistIE,
|
|
@@ -2521,6 +2530,7 @@ from .yappy import (
|
|
|
2521
2530
|
YappyIE,
|
|
2522
2531
|
YappyProfileIE,
|
|
2523
2532
|
)
|
|
2533
|
+
from .yfanefa import YfanefaIE
|
|
2524
2534
|
from .yle_areena import YleAreenaIE
|
|
2525
2535
|
from .youjizz import YouJizzIE
|
|
2526
2536
|
from .youku import (
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from .common import InfoExtractor
|
|
4
|
+
from ..utils.traversal import traverse_obj
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BitmovinIE(InfoExtractor):
|
|
8
|
+
_VALID_URL = r'https?://streams\.bitmovin\.com/(?P<id>\w+)'
|
|
9
|
+
_EMBED_REGEX = [r'<iframe\b[^>]+\bsrc=["\'](?P<url>(?:https?:)?//streams\.bitmovin\.com/(?P<id>\w+)[^"\']+)']
|
|
10
|
+
_TESTS = [{
|
|
11
|
+
'url': 'https://streams.bitmovin.com/cqkl1t5giv3lrce7pjbg/embed',
|
|
12
|
+
'info_dict': {
|
|
13
|
+
'id': 'cqkl1t5giv3lrce7pjbg',
|
|
14
|
+
'ext': 'mp4',
|
|
15
|
+
'title': 'Developing Osteopathic Residents as Faculty',
|
|
16
|
+
'thumbnail': 'https://streams.bitmovin.com/cqkl1t5giv3lrce7pjbg/poster',
|
|
17
|
+
},
|
|
18
|
+
'params': {'skip_download': 'm3u8'},
|
|
19
|
+
}, {
|
|
20
|
+
'url': 'https://streams.bitmovin.com/cgl9rh94uvs51rqc8jhg/share',
|
|
21
|
+
'info_dict': {
|
|
22
|
+
'id': 'cgl9rh94uvs51rqc8jhg',
|
|
23
|
+
'ext': 'mp4',
|
|
24
|
+
'title': 'Big Buck Bunny (Streams Docs)',
|
|
25
|
+
'thumbnail': 'https://streams.bitmovin.com/cgl9rh94uvs51rqc8jhg/poster',
|
|
26
|
+
},
|
|
27
|
+
'params': {'skip_download': 'm3u8'},
|
|
28
|
+
}]
|
|
29
|
+
_WEBPAGE_TESTS = [{
|
|
30
|
+
# bitmovin-stream web component
|
|
31
|
+
'url': 'https://www.institutionalinvestor.com/article/2bsw1in1l9k68mp9kritc/video-war-stories-over-board-games/best-case-i-get-fired-war-stories',
|
|
32
|
+
'info_dict': {
|
|
33
|
+
'id': 'cuiumeil6g115lc4li3g',
|
|
34
|
+
'ext': 'mp4',
|
|
35
|
+
'title': '[media] War Stories over Board Games: “Best Case: I Get Fired” ',
|
|
36
|
+
'thumbnail': 'https://streams.bitmovin.com/cuiumeil6g115lc4li3g/poster',
|
|
37
|
+
},
|
|
38
|
+
'params': {'skip_download': 'm3u8'},
|
|
39
|
+
}, {
|
|
40
|
+
# iframe embed
|
|
41
|
+
'url': 'https://www.clearblueionizer.com/en/pool-ionizers/mineral-pool-vs-saltwater-pool/',
|
|
42
|
+
'info_dict': {
|
|
43
|
+
'id': 'cvpvfsm1pf7itg7cfvtg',
|
|
44
|
+
'ext': 'mp4',
|
|
45
|
+
'title': 'Pool Ionizer vs. Salt Chlorinator',
|
|
46
|
+
'thumbnail': 'https://streams.bitmovin.com/cvpvfsm1pf7itg7cfvtg/poster',
|
|
47
|
+
},
|
|
48
|
+
'params': {'skip_download': 'm3u8'},
|
|
49
|
+
}]
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def _extract_embed_urls(cls, url, webpage):
|
|
53
|
+
yield from super()._extract_embed_urls(url, webpage)
|
|
54
|
+
for stream_id in re.findall(r'<bitmovin-stream\b[^>]*\bstream-id=["\'](?P<id>\w+)', webpage):
|
|
55
|
+
yield f'https://streams.bitmovin.com/{stream_id}'
|
|
56
|
+
|
|
57
|
+
def _real_extract(self, url):
|
|
58
|
+
video_id = self._match_id(url)
|
|
59
|
+
|
|
60
|
+
player_config = self._download_json(
|
|
61
|
+
f'https://streams.bitmovin.com/{video_id}/config', video_id)['sources']
|
|
62
|
+
|
|
63
|
+
formats, subtitles = self._extract_m3u8_formats_and_subtitles(
|
|
64
|
+
player_config['hls'], video_id, 'mp4')
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
'id': video_id,
|
|
68
|
+
'formats': formats,
|
|
69
|
+
'subtitles': subtitles,
|
|
70
|
+
**traverse_obj(player_config, {
|
|
71
|
+
'title': ('title', {str}),
|
|
72
|
+
'thumbnail': ('poster', {str}),
|
|
73
|
+
}),
|
|
74
|
+
}
|
yt_dlp/extractor/floatplane.py
CHANGED
|
@@ -109,6 +109,17 @@ class FloatplaneBaseIE(InfoExtractor):
|
|
|
109
109
|
'hls_media_playlist_data': m3u8_data,
|
|
110
110
|
'hls_aes': hls_aes or None,
|
|
111
111
|
})
|
|
112
|
+
|
|
113
|
+
subtitles = {}
|
|
114
|
+
automatic_captions = {}
|
|
115
|
+
for sub_data in traverse_obj(metadata, ('textTracks', lambda _, v: url_or_none(v['src']))):
|
|
116
|
+
sub_lang = sub_data.get('language') or 'en'
|
|
117
|
+
sub_entry = {'url': sub_data['src']}
|
|
118
|
+
if sub_data.get('generated'):
|
|
119
|
+
automatic_captions.setdefault(sub_lang, []).append(sub_entry)
|
|
120
|
+
else:
|
|
121
|
+
subtitles.setdefault(sub_lang, []).append(sub_entry)
|
|
122
|
+
|
|
112
123
|
items.append({
|
|
113
124
|
**common_info,
|
|
114
125
|
'id': media_id,
|
|
@@ -118,6 +129,8 @@ class FloatplaneBaseIE(InfoExtractor):
|
|
|
118
129
|
'thumbnail': ('thumbnail', 'path', {url_or_none}),
|
|
119
130
|
}),
|
|
120
131
|
'formats': formats,
|
|
132
|
+
'subtitles': subtitles,
|
|
133
|
+
'automatic_captions': automatic_captions,
|
|
121
134
|
})
|
|
122
135
|
|
|
123
136
|
post_info = {
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from .common import InfoExtractor
|
|
4
|
+
from ..utils import int_or_none, parse_iso8601, url_or_none
|
|
5
|
+
from ..utils.traversal import traverse_obj
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FrontoBaseIE(InfoExtractor):
|
|
9
|
+
def _get_auth_headers(self, url):
|
|
10
|
+
return traverse_obj(self._get_cookies(url), {
|
|
11
|
+
'authorization': ('frAccessToken', 'value', {lambda token: f'Bearer {token}' if token else None}),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FrontroVideoBaseIE(FrontoBaseIE):
|
|
16
|
+
_CHANNEL_ID = None
|
|
17
|
+
|
|
18
|
+
def _real_extract(self, url):
|
|
19
|
+
video_id = self._match_id(url)
|
|
20
|
+
|
|
21
|
+
metadata = self._download_json(
|
|
22
|
+
'https://api.frontrow.cc/query', video_id, data=json.dumps({
|
|
23
|
+
'operationName': 'Video',
|
|
24
|
+
'variables': {'channelID': self._CHANNEL_ID, 'videoID': video_id},
|
|
25
|
+
'query': '''query Video($channelID: ID!, $videoID: ID!) {
|
|
26
|
+
video(ChannelID: $channelID, VideoID: $videoID) {
|
|
27
|
+
... on Video {title description updatedAt thumbnail createdAt duration likeCount comments views url hasAccess}
|
|
28
|
+
}
|
|
29
|
+
}''',
|
|
30
|
+
}).encode(), headers={
|
|
31
|
+
'content-type': 'application/json',
|
|
32
|
+
**self._get_auth_headers(url),
|
|
33
|
+
})['data']['video']
|
|
34
|
+
if not traverse_obj(metadata, 'hasAccess'):
|
|
35
|
+
self.raise_login_required()
|
|
36
|
+
|
|
37
|
+
formats, subtitles = self._extract_m3u8_formats_and_subtitles(metadata['url'], video_id)
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
'id': video_id,
|
|
41
|
+
'formats': formats,
|
|
42
|
+
'subtitles': subtitles,
|
|
43
|
+
**traverse_obj(metadata, {
|
|
44
|
+
'title': ('title', {str}),
|
|
45
|
+
'description': ('description', {str}),
|
|
46
|
+
'thumbnail': ('thumbnail', {url_or_none}),
|
|
47
|
+
'timestamp': ('createdAt', {parse_iso8601}),
|
|
48
|
+
'modified_timestamp': ('updatedAt', {parse_iso8601}),
|
|
49
|
+
'duration': ('duration', {int_or_none}),
|
|
50
|
+
'like_count': ('likeCount', {int_or_none}),
|
|
51
|
+
'comment_count': ('comments', {int_or_none}),
|
|
52
|
+
'view_count': ('views', {int_or_none}),
|
|
53
|
+
}),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class FrontroGroupBaseIE(FrontoBaseIE):
|
|
58
|
+
_CHANNEL_ID = None
|
|
59
|
+
_VIDEO_EXTRACTOR = None
|
|
60
|
+
_VIDEO_URL_TMPL = None
|
|
61
|
+
|
|
62
|
+
def _real_extract(self, url):
|
|
63
|
+
group_id = self._match_id(url)
|
|
64
|
+
|
|
65
|
+
metadata = self._download_json(
|
|
66
|
+
'https://api.frontrow.cc/query', group_id, note='Downloading playlist metadata',
|
|
67
|
+
data=json.dumps({
|
|
68
|
+
'operationName': 'PaginatedStaticPageContainer',
|
|
69
|
+
'variables': {'channelID': self._CHANNEL_ID, 'first': 500, 'pageContainerID': group_id},
|
|
70
|
+
'query': '''query PaginatedStaticPageContainer($channelID: ID!, $pageContainerID: ID!) {
|
|
71
|
+
pageContainer(ChannelID: $channelID, PageContainerID: $pageContainerID) {
|
|
72
|
+
... on StaticPageContainer { id title updatedAt createdAt itemRefs {edges {node {
|
|
73
|
+
id contentItem { ... on ItemVideo { videoItem: item {
|
|
74
|
+
id
|
|
75
|
+
}}}
|
|
76
|
+
}}}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}''',
|
|
80
|
+
}).encode(), headers={
|
|
81
|
+
'content-type': 'application/json',
|
|
82
|
+
**self._get_auth_headers(url),
|
|
83
|
+
})['data']['pageContainer']
|
|
84
|
+
|
|
85
|
+
entries = []
|
|
86
|
+
for video_id in traverse_obj(metadata, (
|
|
87
|
+
'itemRefs', 'edges', ..., 'node', 'contentItem', 'videoItem', 'id', {str}),
|
|
88
|
+
):
|
|
89
|
+
entries.append(self.url_result(
|
|
90
|
+
self._VIDEO_URL_TMPL % video_id, self._VIDEO_EXTRACTOR, video_id))
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
'_type': 'playlist',
|
|
94
|
+
'id': group_id,
|
|
95
|
+
'entries': entries,
|
|
96
|
+
**traverse_obj(metadata, {
|
|
97
|
+
'title': ('title', {str}),
|
|
98
|
+
'timestamp': ('createdAt', {parse_iso8601}),
|
|
99
|
+
'modified_timestamp': ('updatedAt', {parse_iso8601}),
|
|
100
|
+
}),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TheChosenIE(FrontroVideoBaseIE):
|
|
105
|
+
_CHANNEL_ID = '12884901895'
|
|
106
|
+
|
|
107
|
+
_VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/video/(?P<id>[0-9]+)'
|
|
108
|
+
_TESTS = [{
|
|
109
|
+
'url': 'https://watch.thechosen.tv/video/184683594325',
|
|
110
|
+
'md5': '3f878b689588c71b38ec9943c54ff5b0',
|
|
111
|
+
'info_dict': {
|
|
112
|
+
'id': '184683594325',
|
|
113
|
+
'ext': 'mp4',
|
|
114
|
+
'title': 'Season 3 Episode 2: Two by Two',
|
|
115
|
+
'description': 'md5:174c373756ecc8df46b403f4fcfbaf8c',
|
|
116
|
+
'comment_count': int,
|
|
117
|
+
'view_count': int,
|
|
118
|
+
'like_count': int,
|
|
119
|
+
'duration': 4212,
|
|
120
|
+
'thumbnail': r're:https://fastly\.frontrowcdn\.com/channels/12884901895/VIDEO_THUMBNAIL/184683594325/',
|
|
121
|
+
'timestamp': 1698954546,
|
|
122
|
+
'upload_date': '20231102',
|
|
123
|
+
'modified_timestamp': int,
|
|
124
|
+
'modified_date': str,
|
|
125
|
+
},
|
|
126
|
+
}, {
|
|
127
|
+
'url': 'https://watch.thechosen.tv/video/184683596189',
|
|
128
|
+
'md5': 'd581562f9d29ce82f5b7770415334151',
|
|
129
|
+
'info_dict': {
|
|
130
|
+
'id': '184683596189',
|
|
131
|
+
'ext': 'mp4',
|
|
132
|
+
'title': 'Season 4 Episode 8: Humble',
|
|
133
|
+
'description': 'md5:20a57bead43da1cf77cd5b0fe29bbc76',
|
|
134
|
+
'comment_count': int,
|
|
135
|
+
'view_count': int,
|
|
136
|
+
'like_count': int,
|
|
137
|
+
'duration': 5092,
|
|
138
|
+
'thumbnail': r're:https://fastly\.frontrowcdn\.com/channels/12884901895/VIDEO_THUMBNAIL/184683596189/',
|
|
139
|
+
'timestamp': 1715019474,
|
|
140
|
+
'upload_date': '20240506',
|
|
141
|
+
'modified_timestamp': int,
|
|
142
|
+
'modified_date': str,
|
|
143
|
+
},
|
|
144
|
+
}]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TheChosenGroupIE(FrontroGroupBaseIE):
|
|
148
|
+
_CHANNEL_ID = '12884901895'
|
|
149
|
+
_VIDEO_EXTRACTOR = TheChosenIE
|
|
150
|
+
_VIDEO_URL_TMPL = 'https://watch.thechosen.tv/video/%s'
|
|
151
|
+
|
|
152
|
+
_VALID_URL = r'https?://(?:www\.)?watch\.thechosen\.tv/group/(?P<id>[0-9]+)'
|
|
153
|
+
_TESTS = [{
|
|
154
|
+
'url': 'https://watch.thechosen.tv/group/309237658592',
|
|
155
|
+
'info_dict': {
|
|
156
|
+
'id': '309237658592',
|
|
157
|
+
'title': 'Season 3',
|
|
158
|
+
'timestamp': 1746203969,
|
|
159
|
+
'upload_date': '20250502',
|
|
160
|
+
'modified_timestamp': int,
|
|
161
|
+
'modified_date': str,
|
|
162
|
+
},
|
|
163
|
+
'playlist_count': 8,
|
|
164
|
+
}]
|