mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-06-17 16:24:54 +00:00
Compare commits
No commits in common. "master" and "2026.06.09" have entirely different histories.
master
...
2026.06.09
@ -395,7 +395,6 @@ banned-from = [
|
||||
"yt_dlp.utils.bytes_to_intlist".msg = "Use `list` instead."
|
||||
"yt_dlp.utils.intlist_to_bytes".msg = "Use `bytes` instead."
|
||||
"yt_dlp.utils.jwt_encode_hs256".msg = "Use `yt_dlp.utils.jwt_encode` instead."
|
||||
"yt_dlp.utils.make_dir".msg = "Use `yt_dlp.utils.make_parent_dirs` instead."
|
||||
"yt_dlp.utils.decodeArgument".msg = "Do not use"
|
||||
"yt_dlp.utils.decodeFilename".msg = "Do not use"
|
||||
"yt_dlp.utils.encodeFilename".msg = "Do not use"
|
||||
|
||||
@ -327,12 +327,6 @@ class TestUtil(unittest.TestCase):
|
||||
with self.assertRaises(_UnsafeExtensionError):
|
||||
prepend_extension('abc.unexpected_ext', ext, 'ext')
|
||||
|
||||
# Test allow-unsafe-ext compat option
|
||||
_UnsafeExtensionError._enabled = False
|
||||
self.assertEqual(prepend_extension('abc.ext', 'un/safe'), 'abc.un/safe.ext')
|
||||
# Re-enable sanitization for other tests
|
||||
_UnsafeExtensionError._enabled = True
|
||||
|
||||
def test_replace_extension(self):
|
||||
self.assertEqual(replace_extension('abc.ext', 'temp'), 'abc.temp')
|
||||
self.assertEqual(replace_extension('abc.ext', 'temp', 'ext'), 'abc.temp')
|
||||
@ -351,12 +345,6 @@ class TestUtil(unittest.TestCase):
|
||||
with self.assertRaises(_UnsafeExtensionError):
|
||||
replace_extension('abc.unexpected_ext', ext, 'ext')
|
||||
|
||||
# Test allow-unsafe-ext compat option
|
||||
_UnsafeExtensionError._enabled = False
|
||||
self.assertEqual(replace_extension('abc.ext', 'bin'), 'abc.bin')
|
||||
# Re-enable sanitization for other tests
|
||||
_UnsafeExtensionError._enabled = True
|
||||
|
||||
def test_subtitles_filename(self):
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt'), 'abc.en.vtt')
|
||||
self.assertEqual(subtitles_filename('abc.ext', 'en', 'vtt', 'ext'), 'abc.en.vtt')
|
||||
@ -2172,10 +2160,6 @@ Line 1
|
||||
headers6 = HTTPHeaderDict(a=1, b=2)
|
||||
self.assertEqual(pickle.loads(pickle.dumps(headers6)), headers6)
|
||||
|
||||
headers7 = HTTPHeaderDict()
|
||||
headers7 |= {'X-dlp': 'data'}
|
||||
self.assertEqual(headers7.sensitive(), {'X-dlp': 'data'})
|
||||
|
||||
def test_extract_basic_auth(self):
|
||||
assert extract_basic_auth('http://:foo.bar') == ('http://:foo.bar', None)
|
||||
assert extract_basic_auth('http://foo.bar') == ('http://foo.bar', None)
|
||||
|
||||
@ -139,7 +139,7 @@ from .utils import (
|
||||
join_nonempty,
|
||||
locked_file,
|
||||
make_archive_id,
|
||||
make_parent_dirs,
|
||||
make_dir,
|
||||
number_of_digits,
|
||||
orderedSet,
|
||||
orderedSet_from_options,
|
||||
@ -2036,12 +2036,7 @@ class YoutubeDL:
|
||||
raise Exception(f'Invalid result type: {result_type}')
|
||||
|
||||
def _ensure_dir_exists(self, path):
|
||||
try:
|
||||
make_parent_dirs(path)
|
||||
return True
|
||||
except OSError as e:
|
||||
self.report_error(f'Unable to create directory: {e}')
|
||||
return False
|
||||
return make_dir(path, self.report_error)
|
||||
|
||||
@staticmethod
|
||||
def _playlist_infodict(ie_result, strict=False, **kwargs):
|
||||
|
||||
@ -619,7 +619,7 @@ def validate_options(opts):
|
||||
warnings.append(
|
||||
'Using allow-unsafe-ext opens you up to potential attacks. '
|
||||
'Use with great care!')
|
||||
_UnsafeExtensionError._enabled = False
|
||||
_UnsafeExtensionError.sanitize_extension = lambda x, prepend=False: x
|
||||
|
||||
return warnings, deprecation_warnings
|
||||
|
||||
|
||||
@ -209,7 +209,7 @@ class CurlFD(ExternalFD):
|
||||
return False
|
||||
|
||||
cls.exe = path
|
||||
cls._curl_version = version_tuple(parts[1], lenient=True)
|
||||
cls._curl_version = version_tuple(parts[1])
|
||||
return path
|
||||
|
||||
def _make_cmd(self, tmpfilename, info_dict):
|
||||
|
||||
@ -119,7 +119,7 @@ body > figure > img {
|
||||
fragments=fragments,
|
||||
frag_boundary=frag_boundary,
|
||||
title=title,
|
||||
).encode()
|
||||
)
|
||||
|
||||
ctx['dest_stream'].write((
|
||||
'MIME-Version: 1.0\r\n'
|
||||
@ -135,7 +135,7 @@ body > figure > img {
|
||||
'Content-Type: text/html; charset=utf-8\r\n'
|
||||
f'Content-Length: {len(stub)}\r\n'
|
||||
'\r\n'
|
||||
).encode() + stub + b'\r\n')
|
||||
f'{stub}\r\n').encode())
|
||||
extra_state['header_written'] = True
|
||||
|
||||
for i, fragment in enumerate(fragments):
|
||||
|
||||
@ -421,7 +421,6 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||
'id': '224',
|
||||
'ext': 'mp3',
|
||||
'title': 'Bandcamp Weekly, 2017-04-04',
|
||||
'episode': 'Magic Moments',
|
||||
'description': 'md5:5d48150916e8e02d030623a48512c874',
|
||||
'thumbnail': 'https://f4.bcbits.com/img/9982549_0.jpg',
|
||||
'series': 'Bandcamp Weekly',
|
||||
@ -441,23 +440,22 @@ class BandcampWeeklyIE(BandcampIE): # XXX: Do not subclass from concrete IE
|
||||
def _real_extract(self, url):
|
||||
show_id = self._match_id(url)
|
||||
show_data = self._download_json(
|
||||
'https://bandcamp.com/api/player/2/player_data_web',
|
||||
'https://bandcamp.com/api/bcradio_api/1/get_show',
|
||||
show_id, 'Downloading radio show JSON',
|
||||
data=json.dumps({'item_id': int(show_id), 'item_type': 'radio'}).encode(),
|
||||
headers={'Content-Type': 'application/json'})['tracklist']
|
||||
data=json.dumps({'id': show_id}).encode(),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
audio_data = show_data['compiledTrack']
|
||||
|
||||
stream_url = audio_data['streamUrl']
|
||||
format_id = traverse_obj(stream_url, ({parse_qs}, 'enc', -1))
|
||||
encoding, _, bitrate_str = (format_id or '').partition('-')
|
||||
|
||||
series_title = show_data.get('subtitle')
|
||||
series_title = show_data.get('title')
|
||||
release_timestamp = unified_timestamp(show_data.get('date'))
|
||||
|
||||
return {
|
||||
'id': show_id,
|
||||
'episode_id': show_id,
|
||||
'episode': show_data.get('title'),
|
||||
'title': join_nonempty(series_title, strftime_or_none(release_timestamp, '%Y-%m-%d'), delim=', '),
|
||||
'series': series_title,
|
||||
'thumbnail': format_field(show_data, 'imageId', 'https://f4.bcbits.com/img/%s_0.jpg', default=None),
|
||||
|
||||
@ -65,9 +65,8 @@ class PatreonBaseIE(InfoExtractor):
|
||||
|
||||
class PatreonIE(PatreonBaseIE):
|
||||
IE_NAME = 'patreon'
|
||||
_VALID_URL = r'https?://(?:www\.)?patreon\.com/(?:creation\?hid=|(?:[^/?#]+/)?posts/(?:[\w-]+-)?)(?P<id>\d+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?patreon\.com/(?:creation\?hid=|posts/(?:[\w-]+-)?)(?P<id>\d+)'
|
||||
_TESTS = [{
|
||||
# FIXME: Fails due to no description extracted
|
||||
'url': 'http://www.patreon.com/creation?hid=743933',
|
||||
'md5': 'e25505eec1053a6e6813b8ed369875cc',
|
||||
'info_dict': {
|
||||
@ -108,17 +107,17 @@ class PatreonIE(PatreonBaseIE):
|
||||
'id': 'SU4fj_aEMVw',
|
||||
'ext': 'mp4',
|
||||
'title': 'I\'m on Patreon!',
|
||||
'uploader': 'Traci Oden',
|
||||
'uploader': 'TraciJHines',
|
||||
'thumbnail': 're:^https?://.*$',
|
||||
'upload_date': '20150211',
|
||||
'description': 'md5:8af6425f50bd46fbf29f3db0fc3a8364',
|
||||
'uploader_id': '@TraciOden',
|
||||
'uploader_id': '@TraciHinesMusic',
|
||||
'categories': ['Entertainment'],
|
||||
'duration': 282,
|
||||
'view_count': int,
|
||||
'tags': 'count:39',
|
||||
'age_limit': 0,
|
||||
'channel': 'Traci Oden',
|
||||
'channel': 'TraciJHines',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCGLim4T2loE5rwCMdpCIPVg',
|
||||
'live_status': 'not_live',
|
||||
'like_count': int,
|
||||
@ -126,7 +125,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
'availability': 'public',
|
||||
'channel_follower_count': int,
|
||||
'playable_in_embed': True,
|
||||
'uploader_url': 'https://www.youtube.com/@TraciOden',
|
||||
'uploader_url': 'https://www.youtube.com/@TraciHinesMusic',
|
||||
'comment_count': int,
|
||||
'channel_is_verified': True,
|
||||
'chapters': 'count:4',
|
||||
@ -158,7 +157,6 @@ class PatreonIE(PatreonBaseIE):
|
||||
},
|
||||
'skip': 'Patron-only content',
|
||||
}, {
|
||||
# FIXME: Fails due to no description extracted
|
||||
# m3u8 video (https://github.com/yt-dlp/yt-dlp/issues/2277)
|
||||
'url': 'https://www.patreon.com/posts/video-sketchbook-32452882',
|
||||
'info_dict': {
|
||||
@ -222,7 +220,6 @@ class PatreonIE(PatreonBaseIE):
|
||||
'channel_id': '2147162',
|
||||
'uploader_url': 'https://www.patreon.com/yaboyroshi',
|
||||
},
|
||||
'skip': 'HTTP Error 401 for m3u8 request; site now requires login to play the video',
|
||||
}, {
|
||||
# NSFW vimeo embed URL
|
||||
'url': 'https://www.patreon.com/posts/4k-spiderman-4k-96414599',
|
||||
@ -245,7 +242,6 @@ class PatreonIE(PatreonBaseIE):
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
'expected_warnings': ['Failed to parse XML: not well-formed'],
|
||||
'skip': 'Video removed',
|
||||
}, {
|
||||
# multiple attachments/embeds
|
||||
'url': 'https://www.patreon.com/posts/holy-wars-solos-100601977',
|
||||
@ -289,7 +285,6 @@ class PatreonIE(PatreonBaseIE):
|
||||
},
|
||||
'params': {'getcomments': True},
|
||||
}, {
|
||||
# FIXME: Error: No supported media found in this post
|
||||
# Inlined media in post; uses _extract_from_media_api
|
||||
'url': 'https://www.patreon.com/posts/scottfalco-146966245',
|
||||
'info_dict': {
|
||||
@ -309,26 +304,6 @@ class PatreonIE(PatreonBaseIE):
|
||||
'timestamp': 1767061800,
|
||||
'upload_date': '20251230',
|
||||
},
|
||||
}, {
|
||||
# FIXME: need to extract description
|
||||
'url': 'https://www.patreon.com/Insanimate/posts/meatcanyon-in-142663524',
|
||||
'md5': '132332e3bb345f75d8b471242346dee6',
|
||||
'info_dict': {
|
||||
'id': '142663524',
|
||||
'ext': 'mp4',
|
||||
'title': 'Meatcanyon in Playground',
|
||||
'uploader': 'Insanimate',
|
||||
'uploader_id': '2828146',
|
||||
'uploader_url': 'https://www.patreon.com/Insanimate',
|
||||
'channel_id': '6260877',
|
||||
'channel_url': 'https://www.patreon.com/Insanimate',
|
||||
'channel_follower_count': int,
|
||||
'comment_count': int,
|
||||
'like_count': int,
|
||||
'thumbnail': 're:^https?://.*$',
|
||||
'timestamp': 1762101034,
|
||||
'upload_date': '20251102',
|
||||
},
|
||||
}]
|
||||
_RETURN_TYPE = 'video'
|
||||
_HTTP_HEADERS = {
|
||||
@ -382,7 +357,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
post = self._call_api(
|
||||
f'posts/{video_id}', video_id, query={
|
||||
'fields[media]': 'download_url,mimetype,size_bytes,file_name',
|
||||
'fields[post]': 'comment_count,content,content_teaser_text,cleaned_teaser_text,embed,image,like_count,post_file,published_at,title,current_user_can_view',
|
||||
'fields[post]': 'comment_count,content,embed,image,like_count,post_file,published_at,title,current_user_can_view',
|
||||
'fields[user]': 'full_name,url',
|
||||
'fields[post_tag]': 'value',
|
||||
'fields[campaign]': 'url,name,patron_count',
|
||||
@ -392,7 +367,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
attributes = post['data']['attributes']
|
||||
info = traverse_obj(attributes, {
|
||||
'title': ('title', {str.strip}),
|
||||
'description': (('content', 'content_teaser_text', 'cleaned_teaser_text'), {clean_html}, any),
|
||||
'description': ('content', {clean_html}),
|
||||
'thumbnail': ('image', ('large_url', 'url'), {url_or_none}, any),
|
||||
'timestamp': ('published_at', {parse_iso8601}),
|
||||
'like_count': ('like_count', {int_or_none}),
|
||||
|
||||
@ -4,7 +4,6 @@ import re
|
||||
from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
OnDemandPagedList,
|
||||
filter_dict,
|
||||
format_field,
|
||||
int_or_none,
|
||||
parse_resolution,
|
||||
@ -1359,7 +1358,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'E2E tests',
|
||||
'categories': ['Unknown'],
|
||||
'channel': 'Chocobozzz test channel',
|
||||
'channel': 'Main chocobozzz channel',
|
||||
'channel_id': '5187',
|
||||
'channel_url': 'https://peertube2.cpy.re/video-channels/chocobozzz_channel',
|
||||
'description': 'md5:67daf92c833c41c95db874e18fcb2786',
|
||||
@ -1383,7 +1382,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'E2E tests',
|
||||
'categories': ['Unknown'],
|
||||
'channel': 'Chocobozzz test channel',
|
||||
'channel': 'Main chocobozzz channel',
|
||||
'channel_id': '5187',
|
||||
'channel_url': 'https://peertube2.cpy.re/video-channels/chocobozzz_channel',
|
||||
'description': 'md5:67daf92c833c41c95db874e18fcb2786',
|
||||
@ -1407,7 +1406,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
'ext': 'mp4',
|
||||
'title': 'E2E tests',
|
||||
'categories': ['Unknown'],
|
||||
'channel': 'Chocobozzz test channel',
|
||||
'channel': 'Main chocobozzz channel',
|
||||
'channel_id': '5187',
|
||||
'channel_url': 'https://peertube2.cpy.re/video-channels/chocobozzz_channel',
|
||||
'description': 'md5:67daf92c833c41c95db874e18fcb2786',
|
||||
@ -1453,36 +1452,6 @@ class PeerTubeIE(InfoExtractor):
|
||||
}, {
|
||||
'url': 'peertube:framatube.org:b37a5b9f-e6b5-415c-b700-04a5cd6ec205',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
'url': 'https://videos.john-livingston.fr/w/mna1A6SxZ94cra4hMtjRQm',
|
||||
'md5': '6a5faad22916e41ba4078ef59c33bc9f',
|
||||
'info_dict': {
|
||||
'id': 'mna1A6SxZ94cra4hMtjRQm',
|
||||
'ext': 'mp4',
|
||||
'title': 'test yt-dlp',
|
||||
'description': 'md5:d8556ee790ad9b3fac6f0bb3eb5b67bd',
|
||||
'thumbnail': r're:https?://videos.john-livingston\.fr/lazy-static/thumbnails/.+\.jpg',
|
||||
'timestamp': 1780645286,
|
||||
'upload_date': '20260605',
|
||||
'uploader': 'John Livingston',
|
||||
'uploader_id': '5',
|
||||
'uploader_url': 'https://videos.john-livingston.fr/accounts/john',
|
||||
'channel': 'john_livingston',
|
||||
'channel_id': '4',
|
||||
'channel_url': 'https://videos.john-livingston.fr/video-channels/john_livingston',
|
||||
'license': 'Unknown',
|
||||
'duration': 16,
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'dislike_count': int,
|
||||
'tags': 'count:0',
|
||||
'categories': ['Unknown'],
|
||||
},
|
||||
'params': {
|
||||
'videopassword': 'thepassword',
|
||||
'format': '600p',
|
||||
},
|
||||
'expected_warnings': ['Ignoring subtitle tracks found in the HLS manifest'],
|
||||
}]
|
||||
_WEBPAGE_TESTS = [{
|
||||
'url': 'https://video.macver.org/w/6gvhZpUGQVd4SQ6oYDc9pC',
|
||||
@ -1523,9 +1492,6 @@ class PeerTubeIE(InfoExtractor):
|
||||
'>We are sorry but it seems that PeerTube is not compatible with your web browser.<')):
|
||||
return 'peertube:{}:{}'.format(*mobj.group('host', 'id'))
|
||||
|
||||
def _get_headers(self):
|
||||
return filter_dict({'x-peertube-video-password': self.get_param('videopassword')})
|
||||
|
||||
@classmethod
|
||||
def _extract_embed_urls(cls, url, webpage):
|
||||
embeds = tuple(super()._extract_embed_urls(url, webpage))
|
||||
@ -1539,7 +1505,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
def _call_api(self, host, video_id, path, note=None, errnote=None, fatal=True):
|
||||
return self._download_json(
|
||||
self._API_BASE % (host, video_id, path), video_id,
|
||||
note=note, errnote=errnote, fatal=fatal, headers=self._get_headers())
|
||||
note=note, errnote=errnote, fatal=fatal)
|
||||
|
||||
def _get_subtitles(self, host, video_id):
|
||||
captions = self._call_api(
|
||||
@ -1579,7 +1545,7 @@ class PeerTubeIE(InfoExtractor):
|
||||
if playlist_url := url_or_none(playlist.get('playlistUrl')):
|
||||
is_live = True
|
||||
formats.extend(self._extract_m3u8_formats(
|
||||
playlist_url, video_id, fatal=False, live=True, headers=self._get_headers()))
|
||||
playlist_url, video_id, fatal=False, live=True))
|
||||
playlist_files = playlist.get('files')
|
||||
if not (playlist_files and isinstance(playlist_files, list)):
|
||||
continue
|
||||
@ -1663,8 +1629,6 @@ class PeerTubeIE(InfoExtractor):
|
||||
'subtitles': subtitles,
|
||||
'is_live': is_live,
|
||||
'webpage_url': webpage_url,
|
||||
# Headers are needed for ALL format requests, but not thumbnails
|
||||
'http_headers': self._get_headers(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1037,10 +1037,8 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||
return next_continuation
|
||||
|
||||
return traverse_obj(renderer, (
|
||||
('contents', 'items', 'rows', 'subThreads'), ..., (
|
||||
('continuationItemRenderer', ('continuationEndpoint', ('button', 'buttonRenderer', 'command'))),
|
||||
('continuationItemViewModel', 'continuationCommand', 'innertubeCommand'),
|
||||
),
|
||||
('contents', 'items', 'rows', 'subThreads'), ..., 'continuationItemRenderer',
|
||||
('continuationEndpoint', ('button', 'buttonRenderer', 'command')),
|
||||
), get_all=False, expected_type=cls._extract_continuation_ep_data)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -333,58 +333,20 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
only_once=True)
|
||||
return
|
||||
|
||||
lockup_mdvm = traverse_obj(view_model, ('metadata', 'lockupMetadataViewModel', {dict}))
|
||||
content_mdvm = traverse_obj(lockup_mdvm, ('metadata', 'contentMetadataViewModel', {dict}))
|
||||
|
||||
thumbnail_badge_view_models = traverse_obj(view_model, (
|
||||
'contentImage', 'thumbnailViewModel', 'overlays', ..., (
|
||||
('thumbnailBottomOverlayViewModel', 'badges'),
|
||||
('thumbnailOverlayBadgeViewModel', 'thumbnailBadges'),
|
||||
), ..., 'thumbnailBadgeViewModel', {dict}))
|
||||
duration_text = traverse_obj(thumbnail_badge_view_models, (..., 'text', {str.lower}, any))
|
||||
thumbnail_badge_styles = traverse_obj(thumbnail_badge_view_models, (..., 'badgeStyle', {str}))
|
||||
|
||||
channel_info = traverse_obj(content_mdvm, (
|
||||
'metadataRows', ..., 'metadataParts',
|
||||
lambda _, v: v['text']['commandRuns'][0]['onTap']['innertubeCommand']['browseEndpoint']['browseId'],
|
||||
'text', any, {
|
||||
'channel': ('content', {str}),
|
||||
'channel_id': ('commandRuns', 0, 'onTap', 'innertubeCommand', 'browseEndpoint', 'browseId', {self.ucid_or_none}),
|
||||
'uploader': ('content', {str}),
|
||||
'uploader_id': ('commandRuns', 0, 'onTap', 'innertubeCommand', 'browseEndpoint', 'canonicalBaseUrl', {self.handle_from_url}),
|
||||
}))
|
||||
|
||||
views_and_time = traverse_obj(content_mdvm, (
|
||||
'metadataRows', lambda _, v: 'accessibilityLabel' in v['metadataParts'][-1],
|
||||
'metadataParts', ...))
|
||||
relative_time_text = traverse_obj(views_and_time, (-1, 'text', 'content', {str.lower}))
|
||||
|
||||
badge_styles = traverse_obj(content_mdvm, (
|
||||
'metadataRows', ..., 'badges', ..., 'badgeViewModel', 'badgeStyle', {str}))
|
||||
|
||||
return self.url_result(
|
||||
url, ie, content_id,
|
||||
title=traverse_obj(lockup_mdvm, ('title', 'content', {str})),
|
||||
title=traverse_obj(view_model, (
|
||||
'metadata', 'lockupMetadataViewModel', 'title', 'content', {str})),
|
||||
thumbnails=self._extract_thumbnails(view_model, (
|
||||
'contentImage', *thumb_keys, 'thumbnailViewModel', 'image'), final_key='sources'),
|
||||
duration=parse_duration(duration_text),
|
||||
view_count=(
|
||||
traverse_obj(views_and_time, (0, 'text', 'content', {parse_count}))
|
||||
# view_count isn't always available; only extract if this metadataRow is 2 metadataParts
|
||||
if len(views_and_time) == 2 else None),
|
||||
timestamp=(
|
||||
self._parse_time_text(relative_time_text, report_failure=False)
|
||||
if self._configuration_arg('approximate_date', ie_key=YoutubeTabIE) else None),
|
||||
live_status=(
|
||||
'is_upcoming' if duration_text == 'upcoming'
|
||||
else 'is_live' if 'THUMBNAIL_OVERLAY_BADGE_STYLE_LIVE' in thumbnail_badge_styles
|
||||
else 'was_live' if relative_time_text and 'streamed' in relative_time_text
|
||||
else None),
|
||||
# XXX: We cannot assume 'public' since we have no way to differentiate from 'unlisted'
|
||||
availability=self._availability(needs_subscription='BADGE_MEMBERS_ONLY' in badge_styles),
|
||||
channel_url=format_field(channel_info, 'channel_id', 'https://www.youtube.com/channel/%s', default=None),
|
||||
uploader_url=format_field(channel_info, 'uploader_id', 'https://www.youtube.com/%s', default=None),
|
||||
**channel_info)
|
||||
duration=traverse_obj(view_model, (
|
||||
'contentImage', 'thumbnailViewModel', 'overlays', ...,
|
||||
(('thumbnailBottomOverlayViewModel', 'badges'), ('thumbnailOverlayBadgeViewModel', 'thumbnailBadges')),
|
||||
..., 'thumbnailBadgeViewModel', 'text', {parse_duration}, any)),
|
||||
timestamp=(traverse_obj(view_model, (
|
||||
'metadata', 'lockupMetadataViewModel', 'metadata', 'contentMetadataViewModel', 'metadataRows',
|
||||
..., 'metadataParts', ..., 'text', 'content', {lambda t: self._parse_time_text(t, report_failure=False)}, any))
|
||||
if self._configuration_arg('approximate_date', ie_key=YoutubeTabIE) else None))
|
||||
|
||||
def _rich_entries(self, rich_grid_renderer):
|
||||
if lockup_view_model := traverse_obj(rich_grid_renderer, ('content', 'lockupViewModel', {dict})):
|
||||
@ -1070,14 +1032,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_mincount': 94,
|
||||
'info_dict': {
|
||||
'id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'title': 'Igor DS: ИИ, Наука и Творчество - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать! Здесь сложные технологии встречаются.+\n$',
|
||||
'uploader': 'Igor DS: ИИ, Наука и Творчество ',
|
||||
'title': 'Igor Kleiner - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать на мой канал! Здесь вы найдете видео .{504}/a1/50b/10a$',
|
||||
'uploader': 'Igor Kleiner ',
|
||||
'uploader_id': '@IgorDataScience',
|
||||
'uploader_url': 'https://www.youtube.com/@IgorDataScience',
|
||||
'channel': 'Igor DS: ИИ, Наука и Творчество ',
|
||||
'channel': 'Igor Kleiner ',
|
||||
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'tags': 'count:19',
|
||||
'tags': 'count:23',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'channel_follower_count': int,
|
||||
},
|
||||
@ -1087,13 +1049,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_mincount': 94,
|
||||
'info_dict': {
|
||||
'id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'title': 'Igor DS: ИИ, Наука и Творчество - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать! Здесь сложные технологии встречаются.+\n$',
|
||||
'title': 'Igor Kleiner - Playlists',
|
||||
'description': r're:(?s)Добро пожаловать на мой канал! Здесь вы найдете видео .{504}/a1/50b/10a$',
|
||||
'uploader': 'Igor Kleiner ',
|
||||
'uploader_id': '@IgorDataScience',
|
||||
'uploader_url': 'https://www.youtube.com/@IgorDataScience',
|
||||
'tags': 'count:19',
|
||||
'tags': 'count:23',
|
||||
'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'channel': 'Igor DS: ИИ, Наука и Творчество ',
|
||||
'channel': 'Igor Kleiner ',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
|
||||
'channel_follower_count': int,
|
||||
},
|
||||
@ -1176,89 +1139,90 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_count': 0,
|
||||
}, {
|
||||
'note': 'Home tab',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/featured',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/featured',
|
||||
'info_dict': {
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Home',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Home',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
},
|
||||
'playlist_mincount': 6,
|
||||
'playlist_mincount': 2,
|
||||
}, {
|
||||
'note': 'Videos tab',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/videos',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/videos',
|
||||
'info_dict': {
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Videos',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Videos',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
},
|
||||
'playlist_mincount': 239,
|
||||
'playlist_mincount': 975,
|
||||
}, {
|
||||
'note': 'Videos tab, sorted by popular',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/videos?view=0&sort=p&flow=grid',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/videos?view=0&sort=p&flow=grid',
|
||||
'info_dict': {
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Videos',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Videos',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
},
|
||||
'playlist_mincount': 239,
|
||||
'playlist_mincount': 199,
|
||||
}, {
|
||||
'note': 'Playlists tab',
|
||||
'url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q/playlists',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/playlists',
|
||||
'info_dict': {
|
||||
'id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'title': 'Creative Commons - Playlists',
|
||||
'description': 'md5:7cfc22824277588d26a66054f22d93c8',
|
||||
'uploader': 'Creative Commons',
|
||||
'uploader_id': '@creativecommons',
|
||||
'uploader_url': 'https://www.youtube.com/@creativecommons',
|
||||
'channel': 'Creative Commons',
|
||||
'channel_id': 'UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCTwECeGqMZee77BjdoYtI2Q',
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Playlists',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'uploader': 'lex will',
|
||||
'uploader_id': '@lexwill718',
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'channel': 'lex will',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'channel_follower_count': int,
|
||||
'tags': ['creative commons', 'remix', 'culture', 'nonprofit'],
|
||||
},
|
||||
'playlist_mincount': 20,
|
||||
'playlist_mincount': 17,
|
||||
}, {
|
||||
'note': 'Posts tab',
|
||||
'url': 'https://www.youtube.com/channel/UCtS3BcCw-tITPFYSvkbP0Bg/posts',
|
||||
'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/community',
|
||||
'info_dict': {
|
||||
'id': 'UCtS3BcCw-tITPFYSvkbP0Bg',
|
||||
'title': 'Office Hours Live with Tim Heidecker - Posts',
|
||||
'description': 'md5:01ec1460ea6c6e2aa47d3be9c756559c',
|
||||
'uploader': 'Office Hours Live with Tim Heidecker',
|
||||
'uploader_id': '@OfficeHoursLive',
|
||||
'uploader_url': 'https://www.youtube.com/@OfficeHoursLive',
|
||||
'channel': 'Office Hours Live with Tim Heidecker',
|
||||
'channel_id': 'UCtS3BcCw-tITPFYSvkbP0Bg',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCtS3BcCw-tITPFYSvkbP0Bg',
|
||||
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'title': 'lex will - Posts',
|
||||
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
|
||||
'channel': 'lex will',
|
||||
'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
|
||||
'tags': ['bible', 'history', 'prophesy'],
|
||||
'channel_follower_count': int,
|
||||
'tags': 'count:17',
|
||||
'uploader_url': 'https://www.youtube.com/@lexwill718',
|
||||
'uploader_id': '@lexwill718',
|
||||
'uploader': 'lex will',
|
||||
},
|
||||
'playlist_mincount': 145,
|
||||
'playlist_mincount': 18,
|
||||
'skip': 'This Community isn\'t available',
|
||||
}, {
|
||||
# TODO: fix channel_is_verified extraction
|
||||
'note': 'Search tab',
|
||||
@ -1308,7 +1272,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
},
|
||||
'playlist_count': 96,
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'Large playlist',
|
||||
'url': 'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q',
|
||||
'info_dict': {
|
||||
@ -1333,8 +1296,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'url': 'http://www.youtube.com/user/NASAgovVideo/videos',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
# The 'note' below is outdated: there is no longer a "Load more" button
|
||||
'note': 'Buggy playlist: the webpage has a "Load more" button but it doesn\'t have more videos',
|
||||
'url': 'https://www.youtube.com/playlist?list=UUXw-G3eDE9trcvY2sBMM_aA',
|
||||
'info_dict': {
|
||||
@ -1352,7 +1313,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'uploader': 'Interstellar Movie',
|
||||
'uploader_url': 'https://www.youtube.com/@InterstellarMovie',
|
||||
},
|
||||
'playlist_mincount': 10,
|
||||
'playlist_mincount': 21,
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'Playlist with "show unavailable videos" button',
|
||||
@ -1375,7 +1336,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_mincount': 150,
|
||||
'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
|
||||
}, {
|
||||
# TODO: fix availability extraction
|
||||
'note': 'Playlist with unavailable videos in page 7',
|
||||
'url': 'https://www.youtube.com/playlist?list=UU8l9frL61Yl5KFOl87nIm2w',
|
||||
'info_dict': {
|
||||
@ -1447,7 +1407,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
}, {
|
||||
'url': 'https://www.youtube.com/channel/UCoMdktPbSTixAyNGwb-UYkQ/live',
|
||||
'info_dict': {
|
||||
'id': 'ubIX-TwVqZI', # This will keep changing
|
||||
'id': 'VFGoUmo74wE', # This will keep changing
|
||||
'ext': 'mp4',
|
||||
'title': str,
|
||||
'upload_date': r're:\d{8}',
|
||||
@ -1626,7 +1586,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'playlist_count': 50,
|
||||
'expected_warnings': ['YouTube Music is not directly supported'],
|
||||
}, {
|
||||
# YoutubeTab_25: use to test _extract_lockup_view_model
|
||||
'note': 'unlisted single video playlist',
|
||||
'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_',
|
||||
'info_dict': {
|
||||
@ -1850,7 +1809,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'title': 'Not Just Bikes - Shorts',
|
||||
'tags': 'count:10',
|
||||
'channel_url': 'https://www.youtube.com/channel/UC0intLFzLaudFG-xAvUEO-A',
|
||||
'description': 'md5:2cb3ccdafa58608fa016f1de4930ec54',
|
||||
'description': 'md5:295758591d0d43d8594277be54584da7',
|
||||
'channel_follower_count': int,
|
||||
'channel_id': 'UC0intLFzLaudFG-xAvUEO-A',
|
||||
'channel': 'Not Just Bikes',
|
||||
@ -1872,8 +1831,8 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'channel': '中村悠一',
|
||||
'channel_follower_count': int,
|
||||
'description': 'md5:76b312b48a26c3b0e4d90e2dfc1b417d',
|
||||
'uploader_url': 'https://www.youtube.com/@中村悠一のあそびば',
|
||||
'uploader_id': '@中村悠一のあそびば',
|
||||
'uploader_url': 'https://www.youtube.com/@Yuichi-Nakamura',
|
||||
'uploader_id': '@Yuichi-Nakamura',
|
||||
'uploader': '中村悠一',
|
||||
},
|
||||
'playlist_mincount': 60,
|
||||
@ -2051,7 +2010,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
'channel': '99% Invisible',
|
||||
'uploader_id': '@99percentinvisiblepodcast',
|
||||
},
|
||||
'playlist_mincount': 5,
|
||||
'playlist_count': 5,
|
||||
}, {
|
||||
# Releases tab, with rich entry playlistRenderers (same as Podcasts tab)
|
||||
# TODO: fix channel_is_verified extraction
|
||||
@ -2075,7 +2034,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
# Playlist with only shorts, shown as reel renderers
|
||||
# FIXME: future: YouTube currently doesn't give continuation for this,
|
||||
# may do in future.
|
||||
# TODO: fix availability extraction
|
||||
'url': 'https://www.youtube.com/playlist?list=UUxqPAgubo4coVn9Lx1FuKcg',
|
||||
'info_dict': {
|
||||
'id': 'UUxqPAgubo4coVn9Lx1FuKcg',
|
||||
|
||||
@ -4,7 +4,7 @@ from .common import PostProcessor
|
||||
from ..compat import shutil
|
||||
from ..utils import (
|
||||
PostProcessingError,
|
||||
make_parent_dirs,
|
||||
make_dir,
|
||||
)
|
||||
|
||||
|
||||
@ -42,10 +42,7 @@ class MoveFilesAfterDownloadPP(PostProcessor):
|
||||
self.report_warning(
|
||||
f'Cannot move file "{oldfile}" out of temporary directory since "{newfile}" already exists. ')
|
||||
continue
|
||||
try:
|
||||
make_parent_dirs(newfile)
|
||||
except OSError as e:
|
||||
raise PostProcessingError(f'Unable to create directory: {e}') from e
|
||||
make_dir(newfile, PostProcessingError)
|
||||
self.to_screen(f'Moving file "{oldfile}" to "{newfile}"')
|
||||
shutil.move(oldfile, newfile) # os.rename cannot move between volumes
|
||||
|
||||
|
||||
@ -46,16 +46,4 @@ def jwt_encode_hs256(payload_data, key, headers={}):
|
||||
return header_b64 + b'.' + payload_b64 + b'.' + signature_b64
|
||||
|
||||
|
||||
def make_dir(path, to_screen=None):
|
||||
from . import make_parent_dirs
|
||||
|
||||
try:
|
||||
make_parent_dirs(path)
|
||||
return True
|
||||
except OSError as e:
|
||||
if to_screen is not None:
|
||||
to_screen(f'Unable to create directory: {e}')
|
||||
return False
|
||||
|
||||
|
||||
compiled_regex_type = type(re.compile(''))
|
||||
|
||||
@ -4713,9 +4713,16 @@ def random_uuidv4():
|
||||
return re.sub(r'[xy]', lambda x: _HEX_TABLE[random.randint(0, 15)], 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx')
|
||||
|
||||
|
||||
def make_parent_dirs(path):
|
||||
if dir_name := os.path.dirname(path):
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
def make_dir(path, to_screen=None):
|
||||
try:
|
||||
dn = os.path.dirname(path)
|
||||
if dn:
|
||||
os.makedirs(dn, exist_ok=True)
|
||||
return True
|
||||
except OSError as err:
|
||||
if callable(to_screen) is not None:
|
||||
to_screen(f'unable to create directory {err}')
|
||||
return False
|
||||
|
||||
|
||||
def get_executable_path():
|
||||
@ -5211,17 +5218,12 @@ class _UnsafeExtensionError(Exception):
|
||||
'sbv',
|
||||
])
|
||||
|
||||
_enabled = True
|
||||
|
||||
def __init__(self, extension, /):
|
||||
super().__init__(f'unsafe file extension: {extension!r}')
|
||||
self.extension = extension
|
||||
|
||||
@classmethod
|
||||
def sanitize_extension(cls, extension, /, *, prepend=False, _allowed_exts=()):
|
||||
if not cls._enabled:
|
||||
return extension
|
||||
|
||||
if extension is None:
|
||||
return None
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ class HTTPHeaderDict(dict):
|
||||
other = other.sensitive()
|
||||
if isinstance(other, dict):
|
||||
self.update(other)
|
||||
return self
|
||||
return
|
||||
return NotImplemented
|
||||
|
||||
def __or__(self, other, /) -> typing.Self:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user