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.
Files changed (21) hide show
  1. yt_dlp/downloader/common.py +2 -1
  2. yt_dlp/extractor/_extractors.py +9 -1
  3. yt_dlp/extractor/frontro.py +164 -0
  4. yt_dlp/extractor/lazy_extractors.py +53 -5
  5. yt_dlp/extractor/mave.py +119 -36
  6. yt_dlp/extractor/soundcloud.py +7 -1
  7. yt_dlp/extractor/thisoldhouse.py +33 -37
  8. yt_dlp/extractor/yfanefa.py +67 -0
  9. yt_dlp/extractor/youtube/_video.py +12 -4
  10. yt_dlp/networking/_urllib.py +2 -0
  11. yt_dlp/version.py +3 -3
  12. {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/METADATA +1 -1
  13. {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/RECORD +21 -19
  14. {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
  15. {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
  16. {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
  17. {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
  18. {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
  19. {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/WHEEL +0 -0
  20. {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/entry_points.txt +0 -0
  21. {yt_dlp-2025.11.15.232912.dev0.dist-info → yt_dlp-2025.11.18.232918.dev0.dist-info}/licenses/LICENSE +0 -0
@@ -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
- if available_at := info_dict.get('available_at'):
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'
@@ -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 MaveIE
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
+ }]