1
0
mirror of https://github.com/yt-dlp/yt-dlp.git synced 2025-06-28 01:18:30 +00:00
This commit is contained in:
Simon Sawicki 2025-06-20 21:49:33 +02:00 committed by GitHub
commit fb7e23606e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 7884 additions and 76 deletions

View File

@ -2349,7 +2349,7 @@ #### Developer options
--test Download only part of video for testing extractors --test Download only part of video for testing extractors
--load-pages Load pages dumped by --write-pages --load-pages Load pages dumped by --write-pages
--youtube-print-sig-code For testing youtube signatures --youtube-print-sig-code For testing youtube signatures
--allow-unplayable-formats List unplayable formats also --allow-unplayable-formats List unplayable formats. Implies `--simulate` and `--list-formats`.
--no-allow-unplayable-formats Default --no-allow-unplayable-formats Default
#### Old aliases #### Old aliases

View File

@ -248,7 +248,7 @@ class YoutubeDL:
You can also pass a function. The function takes 'ctx' as You can also pass a function. The function takes 'ctx' as
argument and returns the formats to download. argument and returns the formats to download.
See "build_format_selector" for an implementation See "build_format_selector" for an implementation
allow_unplayable_formats: Allow unplayable formats to be extracted and downloaded. allow_unplayable_formats: Allow unplayable formats to be extracted.
ignore_no_formats_error: Ignore "No video formats" error. Usefull for ignore_no_formats_error: Ignore "No video formats" error. Usefull for
extracting metadata even if the video is not actually extracting metadata even if the video is not actually
available for download (experimental) available for download (experimental)
@ -702,11 +702,14 @@ def process_color_policy(stream):
self.deprecated_feature(system_deprecation.replace('\n', '\n ')) self.deprecated_feature(system_deprecation.replace('\n', '\n '))
if self.params.get('allow_unplayable_formats'): if self.params.get('allow_unplayable_formats'):
from . import _IN_CLI
switch = '--allow-unplayable-formats' if _IN_CLI else 'allow_unplayable_formats'
self.report_warning( self.report_warning(
f'You have asked for {self._format_err("UNPLAYABLE", self.Styles.EMPHASIS)} formats to be listed/downloaded. ' f'{switch} is a {self._format_err("developer option", self.Styles.EMPHASIS)} intended for {self._format_err("debugging", self.Styles.EMPHASIS)}. \n'
'This is a developer option intended for debugging. \n' f' If you experience issues {self._format_err("DO NOT", self.Styles.ERROR)} open a bug report.')
' If you experience any issues while using this option, ' self.params['listformats'] = True
f'{self._format_err("DO NOT", self.Styles.ERROR)} open a bug report') self.params['simulate'] = True
if self.params.get('bidi_workaround', False): if self.params.get('bidi_workaround', False):
try: try:
@ -2849,6 +2852,7 @@ def sanitize_numeric_fields(info):
info_dict['_has_drm'] = any( # or None ensures --clean-infojson removes it info_dict['_has_drm'] = any( # or None ensures --clean-infojson removes it
f.get('has_drm') and f['has_drm'] != 'maybe' for f in formats) or None f.get('has_drm') and f['has_drm'] != 'maybe' for f in formats) or None
if not self.params.get('allow_unplayable_formats'): if not self.params.get('allow_unplayable_formats'):
# Allow bypassing flaky `has_drm` detection
formats = [f for f in formats if not f.get('has_drm') or f['has_drm'] == 'maybe'] formats = [f for f in formats if not f.get('has_drm') or f['has_drm'] == 'maybe']
if formats and all(f.get('acodec') == f.get('vcodec') == 'none' for f in formats): if formats and all(f.get('acodec') == f.get('vcodec') == 'none' for f in formats):
@ -3464,12 +3468,7 @@ def correct_ext(filename, ext=new_ext):
success, real_download = self.dl(temp_filename, info_dict) success, real_download = self.dl(temp_filename, info_dict)
info_dict['__real_download'] = real_download info_dict['__real_download'] = real_download
else: else:
if self.params.get('allow_unplayable_formats'): if not merger.available:
self.report_warning(
'You have requested merging of multiple formats '
'while also allowing unplayable formats to be downloaded. '
'The formats won\'t be merged to prevent data corruption.')
elif not merger.available:
msg = 'You have requested merging of multiple formats but ffmpeg is not installed' msg = 'You have requested merging of multiple formats but ffmpeg is not installed'
if not self.params.get('ignoreerrors'): if not self.params.get('ignoreerrors'):
self.report_error(f'{msg}. Aborting due to --abort-on-error') self.report_error(f'{msg}. Aborting due to --abort-on-error')
@ -3500,7 +3499,7 @@ def correct_ext(filename, ext=new_ext):
info_dict['__real_download'] = info_dict['__real_download'] or real_download info_dict['__real_download'] = info_dict['__real_download'] or real_download
success = success and partial_success success = success and partial_success
if downloaded and merger.available and not self.params.get('allow_unplayable_formats'): if downloaded and merger.available:
info_dict['__postprocessors'].append(merger) info_dict['__postprocessors'].append(merger)
info_dict['__files_to_merge'] = downloaded info_dict['__files_to_merge'] = downloaded
# Even if there were no downloads, it is being merged only now # Even if there were no downloads, it is being merged only now

View File

@ -511,8 +511,7 @@ def report_args_compat(name, value, key1, key2=None, where=None):
opts.postprocessor_args['default'] = opts.postprocessor_args.pop('default-compat') opts.postprocessor_args['default'] = opts.postprocessor_args.pop('default-compat')
opts.postprocessor_args.setdefault('sponskrub', []) opts.postprocessor_args.setdefault('sponskrub', [])
def report_conflict(arg1, opt1, arg2='--allow-unplayable-formats', opt2='allow_unplayable_formats', def report_conflict(arg1, opt1, arg2, opt2, val1=NO_DEFAULT, val2=NO_DEFAULT, default=False):
val1=NO_DEFAULT, val2=NO_DEFAULT, default=False):
if val2 is NO_DEFAULT: if val2 is NO_DEFAULT:
val2 = getattr(opts, opt2) val2 = getattr(opts, opt2)
if not val2: if not val2:
@ -540,21 +539,6 @@ def report_conflict(arg1, opt1, arg2='--allow-unplayable-formats', opt2='allow_u
report_conflict('--sponskrub-cut', 'sponskrub_cut', '--split-chapter', 'split_chapters', report_conflict('--sponskrub-cut', 'sponskrub_cut', '--split-chapter', 'split_chapters',
val1=opts.sponskrub and opts.sponskrub_cut) val1=opts.sponskrub and opts.sponskrub_cut)
# Conflicts with --allow-unplayable-formats
report_conflict('--embed-metadata', 'addmetadata')
report_conflict('--embed-chapters', 'addchapters')
report_conflict('--embed-info-json', 'embed_infojson')
report_conflict('--embed-subs', 'embedsubtitles')
report_conflict('--embed-thumbnail', 'embedthumbnail')
report_conflict('--extract-audio', 'extractaudio')
report_conflict('--fixup', 'fixup', val1=opts.fixup not in (None, 'never', 'ignore'), default='never')
report_conflict('--recode-video', 'recodevideo')
report_conflict('--remove-chapters', 'remove_chapters', default=[])
report_conflict('--remux-video', 'remuxvideo')
report_conflict('--sponskrub', 'sponskrub')
report_conflict('--sponsorblock-remove', 'sponsorblock_remove', default=set())
report_conflict('--xattrs', 'xattrs')
# Fully deprecated options # Fully deprecated options
def report_deprecation(val, old, new=None): def report_deprecation(val, old, new=None):
if not val: if not val:

View File

@ -468,7 +468,6 @@ def can_merge_formats(cls, info_dict, params):
return ( return (
info_dict.get('requested_formats') info_dict.get('requested_formats')
and info_dict.get('protocol') and info_dict.get('protocol')
and not params.get('allow_unplayable_formats')
and 'no-direct-merge' not in params.get('compat_opts', []) and 'no-direct-merge' not in params.get('compat_opts', [])
and cls.can_download(info_dict)) and cls.can_download(info_dict))

View File

@ -256,7 +256,6 @@ def _get_unencrypted_media(self, doc):
media = doc.findall(_add_ns('media')) media = doc.findall(_add_ns('media'))
if not media: if not media:
self.report_error('No media found') self.report_error('No media found')
if not self.params.get('allow_unplayable_formats'):
for e in (doc.findall(_add_ns('drmAdditionalHeader')) for e in (doc.findall(_add_ns('drmAdditionalHeader'))
+ doc.findall(_add_ns('drmAdditionalHeaderSet'))): + doc.findall(_add_ns('drmAdditionalHeaderSet'))):
# If id attribute is missing it's valid for all media nodes # If id attribute is missing it's valid for all media nodes

View File

@ -15,6 +15,7 @@
traverse_obj, traverse_obj,
update_url_query, update_url_query,
urljoin, urljoin,
deprecation_warning,
) )
from ..utils._utils import _request_dump_filename from ..utils._utils import _request_dump_filename
@ -39,6 +40,8 @@ def _has_drm(manifest): # TODO: https://github.com/yt-dlp/yt-dlp/pull/5039
@classmethod @classmethod
def can_download(cls, manifest, info_dict, allow_unplayable_formats=False): def can_download(cls, manifest, info_dict, allow_unplayable_formats=False):
if allow_unplayable_formats:
deprecation_warning('allow_unplayable_formats is not supported', stacklevel=1)
UNSUPPORTED_FEATURES = [ UNSUPPORTED_FEATURES = [
# r'#EXT-X-BYTERANGE', # playlists composed of byte ranges of media files [2] # r'#EXT-X-BYTERANGE', # playlists composed of byte ranges of media files [2]
@ -58,7 +61,6 @@ def can_download(cls, manifest, info_dict, allow_unplayable_formats=False):
# 4. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3.5 # 4. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3.5
# 5. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.2.5 # 5. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.2.5
] ]
if not allow_unplayable_formats:
UNSUPPORTED_FEATURES += [ UNSUPPORTED_FEATURES += [
r'#EXT-X-KEY:METHOD=(?!NONE|AES-128)', # encrypted streams [1], but not necessarily DRM r'#EXT-X-KEY:METHOD=(?!NONE|AES-128)', # encrypted streams [1], but not necessarily DRM
] ]
@ -67,7 +69,6 @@ def check_results():
yield not info_dict.get('is_live') yield not info_dict.get('is_live')
for feature in UNSUPPORTED_FEATURES: for feature in UNSUPPORTED_FEATURES:
yield not re.search(feature, manifest) yield not re.search(feature, manifest)
if not allow_unplayable_formats:
yield not cls._has_drm(manifest) yield not cls._has_drm(manifest)
return all(check_results()) return all(check_results())
@ -91,7 +92,7 @@ def real_download(self, filename, info_dict):
outf.write(s_bytes) outf.write(s_bytes)
s = s_bytes.decode('utf-8', 'ignore') s = s_bytes.decode('utf-8', 'ignore')
can_download, message = self.can_download(s, info_dict, self.params.get('allow_unplayable_formats')), None can_download, message = self.can_download(s, info_dict), None
if can_download: if can_download:
has_ffmpeg = FFmpegFD.available() has_ffmpeg = FFmpegFD.available()
no_crypto = not Cryptodome.AES and '#EXT-X-KEY:METHOD=AES-128' in s no_crypto = not Cryptodome.AES and '#EXT-X-KEY:METHOD=AES-128' in s
@ -105,7 +106,7 @@ def real_download(self, filename, info_dict):
message = ('Live HLS streams are not supported by the native downloader. If this is a livestream, ' message = ('Live HLS streams are not supported by the native downloader. If this is a livestream, '
f'please {install_ffmpeg}add "--downloader ffmpeg --hls-use-mpegts" to your command') f'please {install_ffmpeg}add "--downloader ffmpeg --hls-use-mpegts" to your command')
if not can_download: if not can_download:
if self._has_drm(s) and not self.params.get('allow_unplayable_formats'): if self._has_drm(s):
if info_dict.get('has_drm') and self.params.get('test'): if info_dict.get('has_drm') and self.params.get('test'):
self.to_screen(f'[{self.FD_NAME}] This format is DRM protected', skip_eol=True) self.to_screen(f'[{self.FD_NAME}] This format is DRM protected', skip_eol=True)
else: else:

View File

@ -236,7 +236,7 @@ def _real_extract(self, url):
self._call_api_v1( self._call_api_v1(
f'{video_type}/detail', video_id, fatal=False, query={'tas': 10000, 'contentId': video_id}), f'{video_type}/detail', video_id, fatal=False, query={'tas': 10000, 'contentId': video_id}),
('body', 'results', 'item', {dict})) or {} ('body', 'results', 'item', {dict})) or {}
if not self.get_param('allow_unplayable_formats') and video_data.get('drmProtected'): if video_data.get('drmProtected'):
self.report_drm(video_id) self.report_drm(video_id)
# See https://github.com/yt-dlp/yt-dlp/issues/396 # See https://github.com/yt-dlp/yt-dlp/issues/396

View File

@ -140,13 +140,14 @@ def _real_extract(self, url):
quality = qualities(self._KNOWN_FORMATS) quality = qualities(self._KNOWN_FORMATS)
formats = [] formats = []
has_drm = False
for f in result.get('files', []): for f in result.get('files', []):
f_url = f.get('url') f_url = f.get('url')
content_format = f.get('content_format') content_format = f.get('content_format')
if not f_url: if not f_url:
continue continue
if (not self.get_param('allow_unplayable_formats') if '-MDRM-' in content_format or '-FPS-' in content_format:
and ('-MDRM-' in content_format or '-FPS-' in content_format)): has_drm = True
continue continue
formats.append({ formats.append({
'url': f_url, 'url': f_url,
@ -154,6 +155,8 @@ def _real_extract(self, url):
'quality': quality(content_format), 'quality': quality(content_format),
'filesize': int_or_none(f.get('size_in_bytes')), 'filesize': int_or_none(f.get('size_in_bytes')),
}) })
if not formats and has_drm:
self.report_drm(video_id)
compilation = result.get('compilation') compilation = result.get('compilation')
episode = title if compilation else None episode = title if compilation else None

View File

@ -464,6 +464,7 @@ def sign_url(unsigned_url):
formats = [] formats = []
subtitles = {} subtitles = {}
has_drm = False
for f in flavor_assets: for f in flavor_assets:
# Continue if asset is not ready # Continue if asset is not ready
if f.get('status') != 2: if f.get('status') != 2:
@ -473,7 +474,8 @@ def sign_url(unsigned_url):
if f.get('fileExt') == 'chun': if f.get('fileExt') == 'chun':
continue continue
# DRM-protected video, cannot be decrypted # DRM-protected video, cannot be decrypted
if not self.get_param('allow_unplayable_formats') and f.get('fileExt') == 'wvm': if f.get('fileExt') == 'wvm':
has_drm = True
continue continue
if not f.get('fileExt'): if not f.get('fileExt'):
# QT indicates QuickTime; some videos have broken fileExt # QT indicates QuickTime; some videos have broken fileExt
@ -513,6 +515,9 @@ def sign_url(unsigned_url):
formats.extend(fmts) formats.extend(fmts)
self._merge_subtitles(subs, target=subtitles) self._merge_subtitles(subs, target=subtitles)
if not formats and has_drm:
self.report_drm(entry_id)
if captions: if captions:
for caption in captions.get('objects', []): for caption in captions.get('objects', []):
# Continue if caption is not ready # Continue if caption is not ready

View File

@ -91,11 +91,13 @@ def _extract_info(self, pc, mobile, i, referer):
formats = [] formats = []
urls = [] urls = []
has_drm = False
for stream in pc_item.get('streams', []): for stream in pc_item.get('streams', []):
stream_url = stream.get('url') stream_url = stream.get('url')
if not stream_url or stream_url in urls: if not stream_url or stream_url in urls:
continue continue
if not self.get_param('allow_unplayable_formats') and stream.get('drmProtected'): if stream.get('drmProtected'):
has_drm = True
continue continue
urls.append(stream_url) urls.append(stream_url)
ext = determine_ext(stream_url) ext = determine_ext(stream_url)
@ -159,8 +161,8 @@ def _extract_info(self, pc, mobile, i, referer):
format_id = mobile_url.get('targetMediaPlatform') format_id = mobile_url.get('targetMediaPlatform')
if not media_url or media_url in urls: if not media_url or media_url in urls:
continue continue
if (format_id in ('Widevine', 'SmoothStreaming') if format_id in ('Widevine', 'SmoothStreaming'):
and not self.get_param('allow_unplayable_formats', False)): has_drm = True
continue continue
urls.append(media_url) urls.append(media_url)
ext = determine_ext(media_url) ext = determine_ext(media_url)
@ -179,6 +181,9 @@ def _extract_info(self, pc, mobile, i, referer):
'ext': ext, 'ext': ext,
}) })
if not formats and has_drm:
self.report_drm(video_id)
subtitles = {} subtitles = {}
for flag in mobile_item.get('flags'): for flag in mobile_item.get('flags'):
if flag == 'ClosedCaptions': if flag == 'ClosedCaptions':

View File

@ -29,8 +29,7 @@ def _real_extract(self, url):
'$include': '[HasClosedCaptions]', '$include': '[HasClosedCaptions]',
}) })
if (not self.get_param('allow_unplayable_formats') if try_get(content_package, lambda x: x['Constraints']['Security']['Type']):
and try_get(content_package, lambda x: x['Constraints']['Security']['Type'])):
self.report_drm(content_id) self.report_drm(content_id)
manifest_base_url = content_package_url + 'manifest.' manifest_base_url = content_package_url + 'manifest.'

View File

@ -65,8 +65,7 @@ def process_format_list(format_list, format_id=''):
for format_dict in format_list: for format_dict in format_list:
if not isinstance(format_dict, dict): if not isinstance(format_dict, dict):
continue continue
if (not self.get_param('allow_unplayable_formats') if traverse_obj(format_dict, ('drm', 'keySystem')):
and traverse_obj(format_dict, ('drm', 'keySystem'))):
has_drm = True has_drm = True
continue continue
format_url = url_or_none(format_dict.get('src')) format_url = url_or_none(format_dict.get('src'))

View File

@ -275,8 +275,7 @@ def _real_extract(self, url):
'url': stream_url, 'url': stream_url,
}) })
if not formats: if not formats and drm:
if not self.get_param('allow_unplayable_formats') and drm:
self.report_drm(video_id) self.report_drm(video_id)
info = { info = {

View File

@ -31,7 +31,7 @@ def _extract_video_info(self, url, clip_id):
'ids': clip_id, 'ids': clip_id,
})[0] })[0]
if not self.get_param('allow_unplayable_formats') and video.get('is_protected') is True: if video.get('is_protected') is True:
self.report_drm(clip_id) self.report_drm(clip_id)
formats = [] formats = []

View File

@ -337,8 +337,12 @@ def _real_extract(self, url):
'height': height, 'height': height,
}) })
has_drm = False
mpd_url = None if data.get('isLive') else data.get('urlDash') mpd_url = None if data.get('isLive') else data.get('urlDash')
if mpd_url and (self.get_param('allow_unplayable_formats') or not data.get('drm')): if mpd_url:
if data.get('drm'):
has_drm = True
else:
fmts, subs = self._extract_mpd_formats_and_subtitles( fmts, subs = self._extract_mpd_formats_and_subtitles(
mpd_url, media_id, mpd_id='dash', fatal=False) mpd_url, media_id, mpd_id='dash', fatal=False)
formats.extend(fmts) formats.extend(fmts)
@ -365,6 +369,9 @@ def _real_extract(self, url):
formats.extend(fmts) formats.extend(fmts)
self._merge_subtitles(subs, target=subtitles) self._merge_subtitles(subs, target=subtitles)
if not formats and has_drm:
self.report_drm(media_id)
return { return {
'id': media_id, 'id': media_id,
'formats': formats, 'formats': formats,

View File

@ -237,8 +237,7 @@ def pv(name):
return value or None return value or None
if not formats: if not formats:
if (not self.get_param('allow_unplayable_formats') if xpath_text(video_xml, './Clip/DRM', default=None):
and xpath_text(video_xml, './Clip/DRM', default=None)):
self.report_drm(video_id) self.report_drm(video_id)
ns_st_cds = pv('ns_st_cds') ns_st_cds = pv('ns_st_cds')
if ns_st_cds != 'free': if ns_st_cds != 'free':

View File

@ -111,7 +111,7 @@ def _real_extract(self, url):
playout = self._call_api( playout = self._call_api(
'playout/new/url/' + video_id, video_id)['playout'] 'playout/new/url/' + video_id, video_id)['playout']
if not self.get_param('allow_unplayable_formats') and playout.get('drm'): if playout.get('drm'):
self.report_drm(video_id) self.report_drm(video_id)
formats = self._extract_m3u8_formats(re.sub( formats = self._extract_m3u8_formats(re.sub(

View File

@ -142,7 +142,7 @@ def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
content = self._call_api( content = self._call_api(
'1.5', 'IN/CONTENT/VIDEOURL/VOD/' + video_id, video_id) '1.5', 'IN/CONTENT/VIDEOURL/VOD/' + video_id, video_id)
if not self.get_param('allow_unplayable_formats') and content.get('isEncrypted'): if content.get('isEncrypted'):
self.report_drm(video_id) self.report_drm(video_id)
dash_url = content['videoURL'] dash_url = content['videoURL']
headers = { headers = {

View File

@ -126,8 +126,7 @@ def _real_extract(self, url):
}) })
if not formats: if not formats:
for meta in (info.get('Metas') or []): for meta in (info.get('Metas') or []):
if (not self.get_param('allow_unplayable_formats') if meta.get('Key') == 'Encryption' and meta.get('Value') == '1':
and meta.get('Key') == 'Encryption' and meta.get('Value') == '1'):
self.report_drm(video_id) self.report_drm(video_id)
# Most likely because geo-blocked if no formats and no DRM # Most likely because geo-blocked if no formats and no DRM

View File

@ -68,7 +68,7 @@ def _real_extract(self, url):
}) })
# IsDrm does not necessarily mean the video is DRM protected (see # IsDrm does not necessarily mean the video is DRM protected (see
# https://github.com/ytdl-org/youtube-dl/issues/13994). # https://github.com/ytdl-org/youtube-dl/issues/13994).
if not self.get_param('allow_unplayable_formats') and metadata.get('IsDrm'): if metadata.get('IsDrm'):
self.report_warning('This video is probably DRM protected.', path) self.report_warning('This video is probably DRM protected.', path)
video_id = metadata['IdMedia'] video_id = metadata['IdMedia']
details = metadata['Details'] details = metadata['Details']

View File

@ -261,7 +261,6 @@ def _real_extract(self, url):
formats = [] formats = []
if stream_meta.get('is_drm'): if stream_meta.get('is_drm'):
if not self.get_param('allow_unplayable_formats'):
self.report_drm(video_id) self.report_drm(video_id)
if stream_meta.get('is_premium'): if stream_meta.get('is_premium'):
sources = self._download_json( sources = self._download_json(

7812
yt_dlp/extractor/youtube.py Normal file

File diff suppressed because it is too large Load Diff