yt-dlp 2026.1.19.233146.dev0__py3-none-any.whl → 2026.1.25.233128.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.
@@ -1,5 +1,6 @@
1
1
  import functools
2
2
  import json
3
+ import random
3
4
  import re
4
5
  import urllib.parse
5
6
 
@@ -363,6 +364,20 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
363
364
  continue
364
365
  yield update_url(player_url, query=query_string)
365
366
 
367
+ @staticmethod
368
+ def _generate_blockbuster_headers():
369
+ # Randomize our HTTP header fingerprint to bust the HTTP Error 403 block
370
+ # See https://github.com/yt-dlp/yt-dlp/issues/15526
371
+
372
+ def random_letters(minimum, maximum):
373
+ # Omit vowels so we don't generate valid header names like 'authorization', etc
374
+ return ''.join(random.choices('bcdfghjklmnpqrstvwxz', k=random.randint(minimum, maximum)))
375
+
376
+ return {
377
+ random_letters(8, 24): random_letters(16, 32)
378
+ for _ in range(random.randint(2, 8))
379
+ }
380
+
366
381
  def _real_extract(self, url):
367
382
  url, smuggled_data = unsmuggle_url(url)
368
383
  video_id, is_playlist, playlist_id = self._match_valid_url(url).group('id', 'is_playlist', 'playlist_id')
@@ -425,7 +440,8 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
425
440
  continue
426
441
  if media_type == 'application/x-mpegURL':
427
442
  fmt, subs = self._extract_m3u8_formats_and_subtitles(
428
- media_url, video_id, 'mp4', live=is_live, m3u8_id='hls', fatal=False)
443
+ media_url, video_id, 'mp4', live=is_live, m3u8_id='hls',
444
+ fatal=False, headers=self._generate_blockbuster_headers())
429
445
  formats.extend(fmt)
430
446
  self._merge_subtitles(subs, target=subtitles)
431
447
  else:
@@ -1,4 +1,6 @@
1
+ import base64
1
2
  import functools
3
+ import hashlib
2
4
  import itertools
3
5
  import json
4
6
  import random
@@ -15,6 +17,7 @@ from ..utils import (
15
17
  UnsupportedError,
16
18
  UserNotLive,
17
19
  determine_ext,
20
+ extract_attributes,
18
21
  filter_dict,
19
22
  format_field,
20
23
  int_or_none,
@@ -25,13 +28,13 @@ from ..utils import (
25
28
  qualities,
26
29
  srt_subtitles_timecode,
27
30
  str_or_none,
28
- traverse_obj,
29
31
  truncate_string,
30
32
  try_call,
31
33
  try_get,
32
34
  url_or_none,
33
35
  urlencode_postdata,
34
36
  )
37
+ from ..utils.traversal import find_element, require, traverse_obj
35
38
 
36
39
 
37
40
  class TikTokBaseIE(InfoExtractor):
@@ -217,38 +220,94 @@ class TikTokBaseIE(InfoExtractor):
217
220
  raise ExtractorError('Unable to extract aweme detail info', video_id=aweme_id)
218
221
  return self._parse_aweme_video_app(aweme_detail)
219
222
 
223
+ def _solve_challenge_and_set_cookie(self, webpage):
224
+ challenge_data = traverse_obj(webpage, (
225
+ {find_element(id='cs', html=True)}, {extract_attributes}, 'class',
226
+ filter, {lambda x: f'{x}==='}, {base64.b64decode}, {json.loads}))
227
+
228
+ if not challenge_data:
229
+ if 'Please wait...' in webpage:
230
+ raise ExtractorError('Unable to extract challenge data')
231
+ raise ExtractorError('Unexpected response from webpage request')
232
+
233
+ self.to_screen('Solving JS challenge using native Python implementation')
234
+
235
+ expected_digest = traverse_obj(challenge_data, (
236
+ 'v', 'c', {str}, {base64.b64decode},
237
+ {require('challenge expected digest')}))
238
+
239
+ base_hash = traverse_obj(challenge_data, (
240
+ 'v', 'a', {str}, {base64.b64decode},
241
+ {hashlib.sha256}, {require('challenge base hash')}))
242
+
243
+ for i in range(1_000_001):
244
+ number = str(i).encode()
245
+ test_hash = base_hash.copy()
246
+ test_hash.update(number)
247
+ if test_hash.digest() == expected_digest:
248
+ challenge_data['d'] = base64.b64encode(number).decode()
249
+ break
250
+ else:
251
+ raise ExtractorError('Unable to solve JS challenge')
252
+
253
+ cookie_value = base64.b64encode(
254
+ json.dumps(challenge_data, separators=(',', ':')).encode()).decode()
255
+
256
+ # At time of writing, the cookie name was _wafchallengeid
257
+ cookie_name = traverse_obj(webpage, (
258
+ {find_element(id='wci', html=True)}, {extract_attributes},
259
+ 'class', {require('challenge cookie name')}))
260
+
261
+ # Actual JS sets Max-Age=1, but we need to adjust for --sleep-requests and Python slowness
262
+ expire_time = int(time.time()) + (self.get_param('sleep_interval_requests') or 0) + 2
263
+ self._set_cookie('.tiktok.com', cookie_name, cookie_value, expire_time=expire_time)
264
+
220
265
  def _extract_web_data_and_status(self, url, video_id, fatal=True):
221
266
  video_data, status = {}, -1
222
267
 
223
- res = self._download_webpage_handle(url, video_id, fatal=fatal, impersonate=True)
224
- if res is False:
225
- return video_data, status
268
+ def get_webpage(note='Downloading webpage'):
269
+ res = self._download_webpage_handle(url, video_id, note, fatal=fatal, impersonate=True)
270
+ if res is False:
271
+ return False
226
272
 
227
- webpage, urlh = res
228
- if urllib.parse.urlparse(urlh.url).path == '/login':
229
- message = 'TikTok is requiring login for access to this content'
230
- if fatal:
231
- self.raise_login_required(message)
232
- self.report_warning(f'{message}. {self._login_hint()}')
273
+ webpage, urlh = res
274
+ if urllib.parse.urlparse(urlh.url).path == '/login':
275
+ message = 'TikTok is requiring login for access to this content'
276
+ if fatal:
277
+ self.raise_login_required(message)
278
+ self.report_warning(f'{message}. {self._login_hint()}', video_id=video_id)
279
+ return False
280
+
281
+ return webpage
282
+
283
+ webpage = get_webpage()
284
+ if webpage is False:
233
285
  return video_data, status
234
286
 
235
- if universal_data := self._get_universal_data(webpage, video_id):
236
- self.write_debug('Found universal data for rehydration')
237
- status = traverse_obj(universal_data, ('webapp.video-detail', 'statusCode', {int})) or 0
238
- video_data = traverse_obj(universal_data, ('webapp.video-detail', 'itemInfo', 'itemStruct', {dict}))
287
+ universal_data = self._get_universal_data(webpage, video_id)
288
+ if not universal_data:
289
+ try:
290
+ self._solve_challenge_and_set_cookie(webpage)
291
+ except ExtractorError as e:
292
+ if fatal:
293
+ raise
294
+ self.report_warning(e.orig_msg, video_id=video_id)
295
+ return video_data, status
239
296
 
240
- elif sigi_data := self._get_sigi_state(webpage, video_id):
241
- self.write_debug('Found sigi state data')
242
- status = traverse_obj(sigi_data, ('VideoPage', 'statusCode', {int})) or 0
243
- video_data = traverse_obj(sigi_data, ('ItemModule', video_id, {dict}))
297
+ webpage = get_webpage(note='Downloading webpage with challenge cookie')
298
+ if webpage is False:
299
+ return video_data, status
300
+ universal_data = self._get_universal_data(webpage, video_id)
244
301
 
245
- elif next_data := self._search_nextjs_data(webpage, video_id, default={}):
246
- self.write_debug('Found next.js data')
247
- status = traverse_obj(next_data, ('props', 'pageProps', 'statusCode', {int})) or 0
248
- video_data = traverse_obj(next_data, ('props', 'pageProps', 'itemInfo', 'itemStruct', {dict}))
302
+ if not universal_data:
303
+ message = 'Unable to extract universal data for rehydration'
304
+ if fatal:
305
+ raise ExtractorError(message)
306
+ self.report_warning(message, video_id=video_id)
307
+ return video_data, status
249
308
 
250
- elif fatal:
251
- raise ExtractorError('Unable to extract webpage video data')
309
+ status = traverse_obj(universal_data, ('webapp.video-detail', 'statusCode', {int})) or 0
310
+ video_data = traverse_obj(universal_data, ('webapp.video-detail', 'itemInfo', 'itemStruct', {dict}))
252
311
 
253
312
  if not traverse_obj(video_data, ('video', {dict})) and traverse_obj(video_data, ('isContentClassified', {bool})):
254
313
  message = 'This post may not be comfortable for some audiences. Log in for access'
yt_dlp/version.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # Autogenerated by devscripts/update-version.py
2
2
 
3
- __version__ = '2026.01.19.233146'
3
+ __version__ = '2026.01.25.233128'
4
4
 
5
- RELEASE_GIT_HEAD = 'c8680b65f79cfeb23b342b70ffe1e233902f7933'
5
+ RELEASE_GIT_HEAD = 'e3f0d8b731b40176bcc632bf92cfe5149402b202'
6
6
 
7
7
  VARIANT = 'pip'
8
8
 
@@ -12,4 +12,4 @@ CHANNEL = 'nightly'
12
12
 
13
13
  ORIGIN = 'yt-dlp/yt-dlp-nightly-builds'
14
14
 
15
- _pkg_version = '2026.01.19.233146dev'
15
+ _pkg_version = '2026.01.25.233128dev'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yt-dlp
3
- Version: 2026.1.19.233146.dev0
3
+ Version: 2026.1.25.233128.dev0
4
4
  Summary: A feature-rich command-line audio/video downloader
5
5
  Project-URL: Documentation, https://github.com/yt-dlp/yt-dlp#readme
6
6
  Project-URL: Repository, https://github.com/yt-dlp/yt-dlp
@@ -11,7 +11,7 @@ yt_dlp/options.py,sha256=-PQ5DyRSIU9YBSG2oh0RTHC-eg4EewNt4zewTZTuERM,100632
11
11
  yt_dlp/plugins.py,sha256=EGmR0ydaahNspGrgszTNX4-YjHe93WOOhcw1gf6PZSs,8215
12
12
  yt_dlp/socks.py,sha256=oAuAfWM6jxI8A5hHDLEKq2U2-k9NyMB_z6nrKzNE9fg,8936
13
13
  yt_dlp/update.py,sha256=sY7gNFBQorzs7sEjRrqL5QOsTBNmGGa_FnpTtbxY1vA,25280
14
- yt_dlp/version.py,sha256=T4kCCWhVdyd2aQeokUU24RpqHxt6uzf_V-H9u8jxtwk,360
14
+ yt_dlp/version.py,sha256=jlXeK9_Clnj7MGlfVA4LmG9H4w-KIt4cbBnKAWpNWc4,360
15
15
  yt_dlp/webvtt.py,sha256=ONkXaaNCZcX8pQhJn3iwIKyaQ34BtVDrMEdG6wRNZwM,11451
16
16
  yt_dlp/__pyinstaller/__init__.py,sha256=-c4Zo8nQGKAm8wc_LDscxMtK7zr_YhZwRnC9CMruUBE,72
17
17
  yt_dlp/__pyinstaller/hook-yt_dlp.py,sha256=5Rd0zV2pDskjY1KtT0wsjxv4hStx67sLCjUexsFvFus,1339
@@ -228,7 +228,7 @@ yt_dlp/extractor/curiositystream.py,sha256=bzhwbA9jBy28tWn8m_y08mHbTpDJ6xz0S4XDG
228
228
  yt_dlp/extractor/cybrary.py,sha256=oSlXqw2Qzhu3Wy7utzEn-GreCD5h7KJ_KDcYysMTmao,6288
229
229
  yt_dlp/extractor/dacast.py,sha256=iO7RiFA5hxduDqicYdzKhrphUZWV45K5yolk057M0ko,7498
230
230
  yt_dlp/extractor/dailymail.py,sha256=AyLI8Shgpn0N3Nz4A1Yk_r4soa_sffB74gp3KnxhuII,3577
231
- yt_dlp/extractor/dailymotion.py,sha256=LDk2BqzQyORqv0NRzC6YieCNKGULeM0o878NJDiyOjc,24031
231
+ yt_dlp/extractor/dailymotion.py,sha256=lBHPkTNEfYumkhZj5ped3QZhybhUeTEkhNHfEdCkFJ8,24693
232
232
  yt_dlp/extractor/dailywire.py,sha256=5dCO2J4ThJ-G4pthAaVs_DFlDm2N0Bgu8DB8Mbthvos,4857
233
233
  yt_dlp/extractor/damtomo.py,sha256=wlRzM4PYbME7EfCU03pcw-PRHTeKAu-eIjcw2hvxMwY,5464
234
234
  yt_dlp/extractor/dangalplay.py,sha256=LmBxCY7hnd1UJKCCDvYGOzv7ZCSDaKGrqQfGHqPraxM,9345
@@ -890,7 +890,7 @@ yt_dlp/extractor/thisoldhouse.py,sha256=YLnhWuuA0xMbY766tVDVKi-iaRwwmgOs8sdD-ydB
890
890
  yt_dlp/extractor/thisvid.py,sha256=dxN-I56UieWt92iS2VSXK7IckobVNQ6BYPgvOJ9vWJI,8628
891
891
  yt_dlp/extractor/threeqsdn.py,sha256=zNHqF2ELqulYpoUCp1YYPlf0tyPS2krsrEUkS0Cw8YQ,6219
892
892
  yt_dlp/extractor/threespeak.py,sha256=agG3Ue0h19dAknJHwrK9a3RBQB4aja-5cx1crkOCIUc,4025
893
- yt_dlp/extractor/tiktok.py,sha256=DF97KMx2bxwKfVWAsYs_vqmOSRvk3Yzy-DefqCO0SkI,73875
893
+ yt_dlp/extractor/tiktok.py,sha256=oG320FyjXWBTtLf61aA3t6enQVFY4m7pBr5HPIrTWxo,76135
894
894
  yt_dlp/extractor/tmz.py,sha256=Nu3xReAc7dKyZcxTGwXYjOpDjeMdfLKSIgJMpjSobNI,9626
895
895
  yt_dlp/extractor/tnaflix.py,sha256=PAWzd7LtF97MF-aHSdUOpWpAmLUqBlAhP0FH0T3tpk0,13561
896
896
  yt_dlp/extractor/toggle.py,sha256=unbnd9IcJJOKcpoYSySISehBRbYeqHhr1x-fHmeLba4,7892
@@ -1124,13 +1124,13 @@ yt_dlp/utils/progress.py,sha256=t9kVvJ0oWuEqRzo9fdFbIhHUBtO_8mg348QwZ1faqLo,3261
1124
1124
  yt_dlp/utils/traversal.py,sha256=64E3RcZ56iSX50RI_HbKdDNftkETMLBaEPX791_b7yQ,18265
1125
1125
  yt_dlp/utils/jslib/__init__.py,sha256=CbdJiRA7Eh5PnjF2V4lDTcg0J0XjBMaaq0H4pCfq9Tk,87
1126
1126
  yt_dlp/utils/jslib/devalue.py,sha256=UtcQ1IEzt6HWBjB9Z_6rJMb3y2pFrbHXDNu1rrxXF1c,5583
1127
- yt_dlp-2026.1.19.233146.dev0.data/data/share/bash-completion/completions/yt-dlp,sha256=KKMwJ7JkH-B2rXIR9n4wAHTqn5waHyxzPtVmsMoYkDI,6009
1128
- yt_dlp-2026.1.19.233146.dev0.data/data/share/doc/yt_dlp/README.txt,sha256=ZT_UKIUjR5lFVtB7ctKc9FBVWkmnM-cFMNDpeVSwFfg,166159
1129
- yt_dlp-2026.1.19.233146.dev0.data/data/share/fish/vendor_completions.d/yt-dlp.fish,sha256=L0JADRod-4ew2pvmYGiDUuXFgu1Ac8msk-ackiLYHCo,51632
1130
- yt_dlp-2026.1.19.233146.dev0.data/data/share/man/man1/yt-dlp.1,sha256=blBneUC8ngI9Qjm2w4xDUIxeRQOLw6tnwNVs02fJFNM,160804
1131
- yt_dlp-2026.1.19.233146.dev0.data/data/share/zsh/site-functions/_yt-dlp,sha256=VsiR8Dn2RqbVSZHAqQyDtTfrc3BIBdzwgrcaJqux8kQ,6005
1132
- yt_dlp-2026.1.19.233146.dev0.dist-info/METADATA,sha256=Ml5p4UhzxrsyJinj4O4Ax9e_gvrieI33YqRRqs5tUDA,181945
1133
- yt_dlp-2026.1.19.233146.dev0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
1134
- yt_dlp-2026.1.19.233146.dev0.dist-info/entry_points.txt,sha256=vWfetvzYgZIwDfMW6BjCe0Cy4pmTZEXRNzxAkfYlRJA,103
1135
- yt_dlp-2026.1.19.233146.dev0.dist-info/licenses/LICENSE,sha256=fhLl30uuEsshWBuhV87SDhmGoFCN0Q0Oikq5pM-U6Fw,1211
1136
- yt_dlp-2026.1.19.233146.dev0.dist-info/RECORD,,
1127
+ yt_dlp-2026.1.25.233128.dev0.data/data/share/bash-completion/completions/yt-dlp,sha256=KKMwJ7JkH-B2rXIR9n4wAHTqn5waHyxzPtVmsMoYkDI,6009
1128
+ yt_dlp-2026.1.25.233128.dev0.data/data/share/doc/yt_dlp/README.txt,sha256=ZT_UKIUjR5lFVtB7ctKc9FBVWkmnM-cFMNDpeVSwFfg,166159
1129
+ yt_dlp-2026.1.25.233128.dev0.data/data/share/fish/vendor_completions.d/yt-dlp.fish,sha256=L0JADRod-4ew2pvmYGiDUuXFgu1Ac8msk-ackiLYHCo,51632
1130
+ yt_dlp-2026.1.25.233128.dev0.data/data/share/man/man1/yt-dlp.1,sha256=blBneUC8ngI9Qjm2w4xDUIxeRQOLw6tnwNVs02fJFNM,160804
1131
+ yt_dlp-2026.1.25.233128.dev0.data/data/share/zsh/site-functions/_yt-dlp,sha256=VsiR8Dn2RqbVSZHAqQyDtTfrc3BIBdzwgrcaJqux8kQ,6005
1132
+ yt_dlp-2026.1.25.233128.dev0.dist-info/METADATA,sha256=BypzGNXFzud6-T5bgDjQDheRF-O1s3zqYflP2XBkX7Q,181945
1133
+ yt_dlp-2026.1.25.233128.dev0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
1134
+ yt_dlp-2026.1.25.233128.dev0.dist-info/entry_points.txt,sha256=vWfetvzYgZIwDfMW6BjCe0Cy4pmTZEXRNzxAkfYlRJA,103
1135
+ yt_dlp-2026.1.25.233128.dev0.dist-info/licenses/LICENSE,sha256=fhLl30uuEsshWBuhV87SDhmGoFCN0Q0Oikq5pM-U6Fw,1211
1136
+ yt_dlp-2026.1.25.233128.dev0.dist-info/RECORD,,