yt-dlp 2025.11.15.232912.dev0__py3-none-any.whl → 2025.11.18.232918.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 +9 -1
- yt_dlp/extractor/frontro.py +164 -0
- yt_dlp/extractor/lazy_extractors.py +53 -5
- yt_dlp/extractor/mave.py +119 -36
- yt_dlp/extractor/soundcloud.py +7 -1
- yt_dlp/extractor/thisoldhouse.py +33 -37
- 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.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/METADATA +1 -1
- {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/RECORD +21 -19
- {yt_dlp-2025.11.15.232912.dev0.data → yt_dlp-2025.11.18.232918.dev0.data}/data/share/bash-completion/completions/yt-dlp +0 -0
- {yt_dlp-2025.11.15.232912.dev0.data → yt_dlp-2025.11.18.232918.dev0.data}/data/share/doc/yt_dlp/README.txt +0 -0
- {yt_dlp-2025.11.15.232912.dev0.data → yt_dlp-2025.11.18.232918.dev0.data}/data/share/fish/vendor_completions.d/yt-dlp.fish +0 -0
- {yt_dlp-2025.11.15.232912.dev0.data → yt_dlp-2025.11.18.232918.dev0.data}/data/share/man/man1/yt-dlp.1 +0 -0
- {yt_dlp-2025.11.15.232912.dev0.data → yt_dlp-2025.11.18.232918.dev0.data}/data/share/zsh/site-functions/_yt-dlp +0 -0
- {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/WHEEL +0 -0
- {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/entry_points.txt +0 -0
- {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.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
|
@@ -691,6 +691,10 @@ from .frontendmasters import (
|
|
|
691
691
|
FrontendMastersIE,
|
|
692
692
|
FrontendMastersLessonIE,
|
|
693
693
|
)
|
|
694
|
+
from .frontro import (
|
|
695
|
+
TheChosenGroupIE,
|
|
696
|
+
TheChosenIE,
|
|
697
|
+
)
|
|
694
698
|
from .fujitv import FujiTVFODPlus7IE
|
|
695
699
|
from .funk import FunkIE
|
|
696
700
|
from .funker530 import Funker530IE
|
|
@@ -1094,7 +1098,10 @@ from .markiza import (
|
|
|
1094
1098
|
from .massengeschmacktv import MassengeschmackTVIE
|
|
1095
1099
|
from .masters import MastersIE
|
|
1096
1100
|
from .matchtv import MatchTVIE
|
|
1097
|
-
from .mave import
|
|
1101
|
+
from .mave import (
|
|
1102
|
+
MaveChannelIE,
|
|
1103
|
+
MaveIE,
|
|
1104
|
+
)
|
|
1098
1105
|
from .mbn import MBNIE
|
|
1099
1106
|
from .mdr import MDRIE
|
|
1100
1107
|
from .medaltv import MedalTVIE
|
|
@@ -2523,6 +2530,7 @@ from .yappy import (
|
|
|
2523
2530
|
YappyIE,
|
|
2524
2531
|
YappyProfileIE,
|
|
2525
2532
|
)
|
|
2533
|
+
from .yfanefa import YfanefaIE
|
|
2526
2534
|
from .yle_areena import YleAreenaIE
|
|
2527
2535
|
from .youjizz import YouJizzIE
|
|
2528
2536
|
from .youku import (
|
|
@@ -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
|
+
}]
|