4 f
'You are using an unsupported version of Python. Only Python versions 3.6 and above are supported by yt-dlp' # noqa: F541
6 __license__
= 'Public Domain'
16 from .options
import (
22 workaround_optparse_bug9161
,
24 from .cookies
import SUPPORTED_BROWSERS
45 from .update
import run_update
46 from .downloader
import (
49 from .extractor
import gen_extractors
, list_extractors
50 from .extractor
.common
import InfoExtractor
51 from .extractor
.adobepass
import MSO_INFO
52 from .postprocessor
import (
54 FFmpegSubtitlesConvertorPP
,
55 FFmpegThumbnailsConvertorPP
,
56 FFmpegVideoConvertorPP
,
61 from .YoutubeDL
import YoutubeDL
64 def _real_main(argv
=None):
65 # Compatibility fixes for Windows
66 if sys
.platform
== 'win32':
67 # https://github.com/ytdl-org/youtube-dl/issues/820
68 codecs
.register(lambda name
: codecs
.lookup('utf-8') if name
== 'cp65001' else None)
70 workaround_optparse_bug9161()
72 setproctitle('yt-dlp')
74 parser
, opts
, args
= parseOpts(argv
)
75 warnings
, deprecation_warnings
= [], []
78 if opts
.user_agent
is not None:
79 std_headers
['User-Agent'] = opts
.user_agent
82 if opts
.referer
is not None:
83 std_headers
['Referer'] = opts
.referer
86 std_headers
.update(opts
.headers
)
89 if opts
.dump_user_agent
:
90 write_string(std_headers
['User-Agent'] + '\n', out
=sys
.stdout
)
93 # Batch file verification
95 if opts
.batchfile
is not None:
97 if opts
.batchfile
== '-':
98 write_string('Reading URLs from stdin:\n')
102 expand_path(opts
.batchfile
),
103 'r', encoding
='utf-8', errors
='ignore')
104 batch_urls
= read_batch_urls(batchfd
)
106 write_string('[debug] Batch file urls: ' + repr(batch_urls
) + '\n')
108 sys
.exit('ERROR: batch file %s could not be read' % opts
.batchfile
)
109 all_urls
= batch_urls
+ [url
.strip() for url
in args
] # batch_urls are already striped in read_batch_urls
110 _enc
= preferredencoding()
111 all_urls
= [url
.decode(_enc
, 'ignore') if isinstance(url
, bytes) else url
for url
in all_urls
]
113 if opts
.list_extractors
:
114 for ie
in list_extractors(opts
.age_limit
):
115 write_string(ie
.IE_NAME
+ (' (CURRENTLY BROKEN)' if not ie
.working() else '') + '\n', out
=sys
.stdout
)
116 matchedUrls
= [url
for url
in all_urls
if ie
.suitable(url
)]
117 for mu
in matchedUrls
:
118 write_string(' ' + mu
+ '\n', out
=sys
.stdout
)
120 if opts
.list_extractor_descriptions
:
121 for ie
in list_extractors(opts
.age_limit
):
124 desc
= getattr(ie
, 'IE_DESC', ie
.IE_NAME
)
127 if getattr(ie
, 'SEARCH_KEY', None) is not None:
128 _SEARCHES
= ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
129 _COUNTS
= ('', '5', '10', 'all')
130 desc
+= f
'; "{ie.SEARCH_KEY}:" prefix (Example: "{ie.SEARCH_KEY}{random.choice(_COUNTS)}:{random.choice(_SEARCHES)}")'
131 write_string(desc
+ '\n', out
=sys
.stdout
)
134 table
= [[mso_id
, mso_info
['name']] for mso_id
, mso_info
in MSO_INFO
.items()]
135 write_string('Supported TV Providers:\n' + render_table(['mso', 'mso name'], table
) + '\n', out
=sys
.stdout
)
138 # Conflicting, missing and erroneous options
139 if opts
.format
== 'best':
140 warnings
.append('.\n '.join(
141 '"-f best" selects the best pre-merged format which is often not the best option',
142 'To let yt-dlp download and merge the best available formats, simply do not pass any format selection',
143 'If you know what you are doing and want only the best pre-merged format, use "-f b" instead to suppress this warning'))
144 if opts
.usenetrc
and (opts
.username
is not None or opts
.password
is not None):
145 parser
.error('using .netrc conflicts with giving username/password')
146 if opts
.password
is not None and opts
.username
is None:
147 parser
.error('account username missing\n')
148 if opts
.ap_password
is not None and opts
.ap_username
is None:
149 parser
.error('TV Provider account username missing\n')
150 if opts
.autonumber_size
is not None:
151 if opts
.autonumber_size
<= 0:
152 parser
.error('auto number size must be positive')
153 if opts
.autonumber_start
is not None:
154 if opts
.autonumber_start
< 0:
155 parser
.error('auto number start must be positive or 0')
156 if opts
.username
is not None and opts
.password
is None:
157 opts
.password
= compat_getpass('Type account password and press [Return]: ')
158 if opts
.ap_username
is not None and opts
.ap_password
is None:
159 opts
.ap_password
= compat_getpass('Type TV provider account password and press [Return]: ')
160 if opts
.ratelimit
is not None:
161 numeric_limit
= FileDownloader
.parse_bytes(opts
.ratelimit
)
162 if numeric_limit
is None:
163 parser
.error('invalid rate limit specified')
164 opts
.ratelimit
= numeric_limit
165 if opts
.throttledratelimit
is not None:
166 numeric_limit
= FileDownloader
.parse_bytes(opts
.throttledratelimit
)
167 if numeric_limit
is None:
168 parser
.error('invalid rate limit specified')
169 opts
.throttledratelimit
= numeric_limit
170 if opts
.min_filesize
is not None:
171 numeric_limit
= FileDownloader
.parse_bytes(opts
.min_filesize
)
172 if numeric_limit
is None:
173 parser
.error('invalid min_filesize specified')
174 opts
.min_filesize
= numeric_limit
175 if opts
.max_filesize
is not None:
176 numeric_limit
= FileDownloader
.parse_bytes(opts
.max_filesize
)
177 if numeric_limit
is None:
178 parser
.error('invalid max_filesize specified')
179 opts
.max_filesize
= numeric_limit
180 if opts
.sleep_interval
is not None:
181 if opts
.sleep_interval
< 0:
182 parser
.error('sleep interval must be positive or 0')
183 if opts
.max_sleep_interval
is not None:
184 if opts
.max_sleep_interval
< 0:
185 parser
.error('max sleep interval must be positive or 0')
186 if opts
.sleep_interval
is None:
187 parser
.error('min sleep interval must be specified, use --min-sleep-interval')
188 if opts
.max_sleep_interval
< opts
.sleep_interval
:
189 parser
.error('max sleep interval must be greater than or equal to min sleep interval')
191 opts
.max_sleep_interval
= opts
.sleep_interval
192 if opts
.sleep_interval_subtitles
is not None:
193 if opts
.sleep_interval_subtitles
< 0:
194 parser
.error('subtitles sleep interval must be positive or 0')
195 if opts
.sleep_interval_requests
is not None:
196 if opts
.sleep_interval_requests
< 0:
197 parser
.error('requests sleep interval must be positive or 0')
198 if opts
.ap_mso
and opts
.ap_mso
not in MSO_INFO
:
199 parser
.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
200 if opts
.overwrites
: # --yes-overwrites implies --no-continue
201 opts
.continue_dl
= False
202 if opts
.concurrent_fragment_downloads
<= 0:
203 parser
.error('Concurrent fragments must be positive')
204 if opts
.wait_for_video
is not None:
205 min_wait
, max_wait
, *_
= map(parse_duration
, opts
.wait_for_video
.split('-', 1) + [None])
206 if min_wait
is None or (max_wait
is None and '-' in opts
.wait_for_video
):
207 parser
.error('Invalid time range to wait')
208 elif max_wait
is not None and max_wait
< min_wait
:
209 parser
.error('Minimum time range to wait must not be longer than the maximum')
210 opts
.wait_for_video
= (min_wait
, max_wait
)
212 def parse_retries(retries
, name
=''):
213 if retries
in ('inf', 'infinite'):
214 parsed_retries
= float('inf')
217 parsed_retries
= int(retries
)
218 except (TypeError, ValueError):
219 parser
.error('invalid %sretry count specified' % name
)
220 return parsed_retries
221 if opts
.retries
is not None:
222 opts
.retries
= parse_retries(opts
.retries
)
223 if opts
.fragment_retries
is not None:
224 opts
.fragment_retries
= parse_retries(opts
.fragment_retries
, 'fragment ')
225 if opts
.extractor_retries
is not None:
226 opts
.extractor_retries
= parse_retries(opts
.extractor_retries
, 'extractor ')
227 if opts
.buffersize
is not None:
228 numeric_buffersize
= FileDownloader
.parse_bytes(opts
.buffersize
)
229 if numeric_buffersize
is None:
230 parser
.error('invalid buffer size specified')
231 opts
.buffersize
= numeric_buffersize
232 if opts
.http_chunk_size
is not None:
233 numeric_chunksize
= FileDownloader
.parse_bytes(opts
.http_chunk_size
)
234 if not numeric_chunksize
:
235 parser
.error('invalid http chunk size specified')
236 opts
.http_chunk_size
= numeric_chunksize
237 if opts
.playliststart
<= 0:
238 raise parser
.error('Playlist start must be positive')
239 if opts
.playlistend
not in (-1, None) and opts
.playlistend
< opts
.playliststart
:
240 raise parser
.error('Playlist end must be greater than playlist start')
241 if opts
.extractaudio
:
242 opts
.audioformat
= opts
.audioformat
.lower()
243 if opts
.audioformat
not in ['best'] + list(FFmpegExtractAudioPP
.SUPPORTED_EXTS
):
244 parser
.error('invalid audio format specified')
245 if opts
.audioquality
:
246 opts
.audioquality
= opts
.audioquality
.strip('k').strip('K')
247 audioquality
= int_or_none(float_or_none(opts
.audioquality
)) # int_or_none prevents inf, nan
248 if audioquality
is None or audioquality
< 0:
249 parser
.error('invalid audio quality specified')
250 if opts
.recodevideo
is not None:
251 opts
.recodevideo
= opts
.recodevideo
.replace(' ', '')
252 if not re
.match(FFmpegVideoConvertorPP
.FORMAT_RE
, opts
.recodevideo
):
253 parser
.error('invalid video remux format specified')
254 if opts
.remuxvideo
is not None:
255 opts
.remuxvideo
= opts
.remuxvideo
.replace(' ', '')
256 if not re
.match(FFmpegVideoRemuxerPP
.FORMAT_RE
, opts
.remuxvideo
):
257 parser
.error('invalid video remux format specified')
258 if opts
.convertsubtitles
is not None:
259 if opts
.convertsubtitles
not in FFmpegSubtitlesConvertorPP
.SUPPORTED_EXTS
:
260 parser
.error('invalid subtitle format specified')
261 if opts
.convertthumbnails
is not None:
262 if opts
.convertthumbnails
not in FFmpegThumbnailsConvertorPP
.SUPPORTED_EXTS
:
263 parser
.error('invalid thumbnail format specified')
264 if opts
.cookiesfrombrowser
is not None:
265 opts
.cookiesfrombrowser
= [
266 part
.strip() or None for part
in opts
.cookiesfrombrowser
.split(':', 1)]
267 if opts
.cookiesfrombrowser
[0].lower() not in SUPPORTED_BROWSERS
:
268 parser
.error('unsupported browser specified for cookies')
269 geo_bypass_code
= opts
.geo_bypass_ip_block
or opts
.geo_bypass_country
270 if geo_bypass_code
is not None:
272 GeoUtils
.random_ipv4(geo_bypass_code
)
274 parser
.error('unsupported geo-bypass country or ip-block')
276 if opts
.date
is not None:
277 date
= DateRange
.day(opts
.date
)
279 date
= DateRange(opts
.dateafter
, opts
.datebefore
)
281 compat_opts
= opts
.compat_opts
283 def report_conflict(arg1
, arg2
):
284 warnings
.append(f
'{arg2} is ignored since {arg1} was given')
286 def _unused_compat_opt(name
):
287 if name
not in compat_opts
:
289 compat_opts
.discard(name
)
290 compat_opts
.update(['*%s' % name
])
293 def set_default_compat(compat_name
, opt_name
, default
=True, remove_compat
=True):
294 attr
= getattr(opts
, opt_name
)
295 if compat_name
in compat_opts
:
297 setattr(opts
, opt_name
, not default
)
301 _unused_compat_opt(compat_name
)
304 setattr(opts
, opt_name
, default
)
307 set_default_compat('abort-on-error', 'ignoreerrors', 'only_download')
308 set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
309 set_default_compat('no-clean-infojson', 'clean_infojson')
310 if 'no-attach-info-json' in compat_opts
:
311 if opts
.embed_infojson
:
312 _unused_compat_opt('no-attach-info-json')
314 opts
.embed_infojson
= False
315 if 'format-sort' in compat_opts
:
316 opts
.format_sort
.extend(InfoExtractor
.FormatSort
.ytdl_default
)
317 _video_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat
=False)
318 _audio_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat
=False)
319 if _video_multistreams_set
is False and _audio_multistreams_set
is False:
320 _unused_compat_opt('multistreams')
321 outtmpl_default
= opts
.outtmpl
.get('default')
323 if outtmpl_default
is None:
324 outtmpl_default
= opts
.outtmpl
['default'] = '%(id)s.%(ext)s'
326 report_conflict('--output', '--id')
327 if 'filename' in compat_opts
:
328 if outtmpl_default
is None:
329 outtmpl_default
= opts
.outtmpl
['default'] = '%(title)s-%(id)s.%(ext)s'
331 _unused_compat_opt('filename')
333 def validate_outtmpl(tmpl
, msg
):
334 err
= YoutubeDL
.validate_outtmpl(tmpl
)
336 parser
.error('invalid %s %r: %s' % (msg
, tmpl
, error_to_compat_str(err
)))
338 for k
, tmpl
in opts
.outtmpl
.items():
339 validate_outtmpl(tmpl
, f
'{k} output template')
340 opts
.forceprint
= opts
.forceprint
or []
341 for tmpl
in opts
.forceprint
or []:
342 validate_outtmpl(tmpl
, 'print template')
343 validate_outtmpl(opts
.sponsorblock_chapter_title
, 'SponsorBlock chapter title')
344 for k
, tmpl
in opts
.progress_template
.items():
345 k
= f
'{k[:-6]} console title' if '-title' in k
else f
'{k} progress'
346 validate_outtmpl(tmpl
, f
'{k} template')
348 if opts
.extractaudio
and not opts
.keepvideo
and opts
.format
is None:
349 opts
.format
= 'bestaudio/best'
351 if outtmpl_default
is not None and not os
.path
.splitext(outtmpl_default
)[1] and opts
.extractaudio
:
352 parser
.error('Cannot download a video and extract audio into the same'
353 ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
354 ' template'.format(outtmpl_default
))
356 for f
in opts
.format_sort
:
357 if re
.match(InfoExtractor
.FormatSort
.regex
, f
) is None:
358 parser
.error('invalid format sort string "%s" specified' % f
)
360 def metadataparser_actions(f
):
361 if isinstance(f
, str):
362 cmd
= '--parse-metadata %s' % compat_shlex_quote(f
)
364 actions
= [MetadataFromFieldPP
.to_action(f
)]
365 except Exception as err
:
366 parser
.error(f
'{cmd} is invalid; {err}')
368 cmd
= '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote
, f
))
369 actions
= ((MetadataParserPP
.Actions
.REPLACE
, x
, *f
[1:]) for x
in f
[0].split(','))
371 for action
in actions
:
373 MetadataParserPP
.validate_action(*action
)
374 except Exception as err
:
375 parser
.error(f
'{cmd} is invalid; {err}')
378 if opts
.parse_metadata
is None:
379 opts
.parse_metadata
= []
380 if opts
.metafromtitle
is not None:
381 opts
.parse_metadata
.append('title:%s' % opts
.metafromtitle
)
382 opts
.parse_metadata
= list(itertools
.chain(*map(metadataparser_actions
, opts
.parse_metadata
)))
384 any_getting
= opts
.forceprint
or opts
.geturl
or opts
.gettitle
or opts
.getid
or opts
.getthumbnail
or opts
.getdescription
or opts
.getfilename
or opts
.getformat
or opts
.getduration
or opts
.dumpjson
or opts
.dump_single_json
385 any_printing
= opts
.print_json
386 download_archive_fn
= expand_path(opts
.download_archive
) if opts
.download_archive
is not None else opts
.download_archive
388 # If JSON is not printed anywhere, but comments are requested, save it to file
389 printing_json
= opts
.dumpjson
or opts
.print_json
or opts
.dump_single_json
390 if opts
.getcomments
and not printing_json
:
391 opts
.writeinfojson
= True
393 if opts
.no_sponsorblock
:
394 opts
.sponsorblock_mark
= set()
395 opts
.sponsorblock_remove
= set()
396 sponsorblock_query
= opts
.sponsorblock_mark | opts
.sponsorblock_remove
398 opts
.remove_chapters
= opts
.remove_chapters
or []
400 if (opts
.remove_chapters
or sponsorblock_query
) and opts
.sponskrub
is not False:
402 if opts
.remove_chapters
:
403 report_conflict('--remove-chapters', '--sponskrub')
404 if opts
.sponsorblock_mark
:
405 report_conflict('--sponsorblock-mark', '--sponskrub')
406 if opts
.sponsorblock_remove
:
407 report_conflict('--sponsorblock-remove', '--sponskrub')
408 opts
.sponskrub
= False
409 if opts
.sponskrub_cut
and opts
.split_chapters
and opts
.sponskrub
is not False:
410 report_conflict('--split-chapter', '--sponskrub-cut')
411 opts
.sponskrub_cut
= False
413 if opts
.remuxvideo
and opts
.recodevideo
:
414 report_conflict('--recode-video', '--remux-video')
415 opts
.remuxvideo
= False
417 if opts
.allow_unplayable_formats
:
418 def report_unplayable_conflict(opt_name
, arg
, default
=False, allowed
=None):
419 val
= getattr(opts
, opt_name
)
420 if (not allowed
and val
) or (allowed
and not allowed(val
)):
421 report_conflict('--allow-unplayable-formats', arg
)
422 setattr(opts
, opt_name
, default
)
424 report_unplayable_conflict('extractaudio', '--extract-audio')
425 report_unplayable_conflict('remuxvideo', '--remux-video')
426 report_unplayable_conflict('recodevideo', '--recode-video')
427 report_unplayable_conflict('addmetadata', '--embed-metadata')
428 report_unplayable_conflict('addchapters', '--embed-chapters')
429 report_unplayable_conflict('embed_infojson', '--embed-info-json')
430 opts
.embed_infojson
= False
431 report_unplayable_conflict('embedsubtitles', '--embed-subs')
432 report_unplayable_conflict('embedthumbnail', '--embed-thumbnail')
433 report_unplayable_conflict('xattrs', '--xattrs')
434 report_unplayable_conflict('fixup', '--fixup', default
='never', allowed
=lambda x
: x
in (None, 'never', 'ignore'))
436 report_unplayable_conflict('remove_chapters', '--remove-chapters', default
=[])
437 report_unplayable_conflict('sponsorblock_remove', '--sponsorblock-remove', default
=set())
438 report_unplayable_conflict('sponskrub', '--sponskrub', default
=set())
439 opts
.sponskrub
= False
441 if (opts
.addmetadata
or opts
.sponsorblock_mark
) and opts
.addchapters
is None:
442 opts
.addchapters
= True
445 postprocessors
= list(opts
.add_postprocessors
)
446 if sponsorblock_query
:
447 postprocessors
.append({
448 'key': 'SponsorBlock',
449 'categories': sponsorblock_query
,
450 'api': opts
.sponsorblock_api
,
451 # Run this immediately after extraction is complete
452 'when': 'pre_process'
454 if opts
.parse_metadata
:
455 postprocessors
.append({
456 'key': 'MetadataParser',
457 'actions': opts
.parse_metadata
,
458 # Run this immediately after extraction is complete
459 'when': 'pre_process'
461 if opts
.convertsubtitles
:
462 postprocessors
.append({
463 'key': 'FFmpegSubtitlesConvertor',
464 'format': opts
.convertsubtitles
,
465 # Run this before the actual video download
468 if opts
.convertthumbnails
:
469 postprocessors
.append({
470 'key': 'FFmpegThumbnailsConvertor',
471 'format': opts
.convertthumbnails
,
472 # Run this before the actual video download
475 # Must be after all other before_dl
476 if opts
.exec_before_dl_cmd
:
477 postprocessors
.append({
479 'exec_cmd': opts
.exec_before_dl_cmd
,
482 if opts
.extractaudio
:
483 postprocessors
.append({
484 'key': 'FFmpegExtractAudio',
485 'preferredcodec': opts
.audioformat
,
486 'preferredquality': opts
.audioquality
,
487 'nopostoverwrites': opts
.nopostoverwrites
,
490 postprocessors
.append({
491 'key': 'FFmpegVideoRemuxer',
492 'preferedformat': opts
.remuxvideo
,
495 postprocessors
.append({
496 'key': 'FFmpegVideoConvertor',
497 'preferedformat': opts
.recodevideo
,
499 # If ModifyChapters is going to remove chapters, subtitles must already be in the container.
500 if opts
.embedsubtitles
:
501 already_have_subtitle
= opts
.writesubtitles
and 'no-keep-subs' not in compat_opts
502 postprocessors
.append({
503 'key': 'FFmpegEmbedSubtitle',
504 # already_have_subtitle = True prevents the file from being deleted after embedding
505 'already_have_subtitle': already_have_subtitle
507 if not opts
.writeautomaticsub
and 'no-keep-subs' not in compat_opts
:
508 opts
.writesubtitles
= True
509 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
510 # this was the old behaviour if only --all-sub was given.
511 if opts
.allsubtitles
and not opts
.writeautomaticsub
:
512 opts
.writesubtitles
= True
513 # ModifyChapters must run before FFmpegMetadataPP
514 remove_chapters_patterns
, remove_ranges
= [], []
515 for regex
in opts
.remove_chapters
:
516 if regex
.startswith('*'):
517 dur
= list(map(parse_duration
, regex
[1:].split('-')))
518 if len(dur
) == 2 and all(t
is not None for t
in dur
):
519 remove_ranges
.append(tuple(dur
))
521 parser
.error(f
'invalid --remove-chapters time range {regex!r}. Must be of the form ?start-end')
523 remove_chapters_patterns
.append(re
.compile(regex
))
524 except re
.error
as err
:
525 parser
.error(f
'invalid --remove-chapters regex {regex!r} - {err}')
526 if opts
.remove_chapters
or sponsorblock_query
:
527 postprocessors
.append({
528 'key': 'ModifyChapters',
529 'remove_chapters_patterns': remove_chapters_patterns
,
530 'remove_sponsor_segments': opts
.sponsorblock_remove
,
531 'remove_ranges': remove_ranges
,
532 'sponsorblock_chapter_title': opts
.sponsorblock_chapter_title
,
533 'force_keyframes': opts
.force_keyframes_at_cuts
535 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
536 # FFmpegExtractAudioPP as containers before conversion may not support
537 # metadata (3gp, webm, etc.)
538 # By default ffmpeg preserves metadata applicable for both
539 # source and target containers. From this point the container won't change,
540 # so metadata can be added here.
541 if opts
.addmetadata
or opts
.addchapters
or opts
.embed_infojson
:
542 if opts
.embed_infojson
is None:
543 opts
.embed_infojson
= 'if_exists'
544 postprocessors
.append({
545 'key': 'FFmpegMetadata',
546 'add_chapters': opts
.addchapters
,
547 'add_metadata': opts
.addmetadata
,
548 'add_infojson': opts
.embed_infojson
,
551 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
552 # but must be below EmbedSubtitle and FFmpegMetadata
553 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
554 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
555 if opts
.sponskrub
is not False:
556 postprocessors
.append({
558 'path': opts
.sponskrub_path
,
559 'args': opts
.sponskrub_args
,
560 'cut': opts
.sponskrub_cut
,
561 'force': opts
.sponskrub_force
,
562 'ignoreerror': opts
.sponskrub
is None,
565 if opts
.embedthumbnail
:
566 postprocessors
.append({
567 'key': 'EmbedThumbnail',
568 # already_have_thumbnail = True prevents the file from being deleted after embedding
569 'already_have_thumbnail': opts
.writethumbnail
571 if not opts
.writethumbnail
:
572 opts
.writethumbnail
= True
573 opts
.outtmpl
['pl_thumbnail'] = ''
574 if opts
.split_chapters
:
575 postprocessors
.append({
576 'key': 'FFmpegSplitChapters',
577 'force_keyframes': opts
.force_keyframes_at_cuts
,
579 # XAttrMetadataPP should be run after post-processors that may change file contents
581 postprocessors
.append({'key': 'XAttrMetadata'}
)
582 # Exec must be the last PP
584 postprocessors
.append({
586 'exec_cmd': opts
.exec_cmd
,
587 # Run this only after the files have been moved to their final locations
591 def report_args_compat(arg
, name
):
592 warnings
.append('%s given without specifying name. The arguments will be given to all %s' % (arg
, name
))
594 if 'default' in opts
.external_downloader_args
:
595 report_args_compat('--downloader-args', 'external downloaders')
597 if 'default-compat' in opts
.postprocessor_args
and 'default' not in opts
.postprocessor_args
:
598 report_args_compat('--post-processor-args', 'post-processors')
599 opts
.postprocessor_args
.setdefault('sponskrub', [])
600 opts
.postprocessor_args
['default'] = opts
.postprocessor_args
['default-compat']
602 def report_deprecation(val
, old
, new
=None):
605 deprecation_warnings
.append(
606 f
'{old} is deprecated and may be removed in a future version. Use {new} instead' if new
607 else f
'{old} is deprecated and may not work as expected')
609 report_deprecation(opts
.sponskrub
, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove')
610 report_deprecation(not opts
.prefer_ffmpeg
, '--prefer-avconv', 'ffmpeg')
611 report_deprecation(opts
.include_ads
, '--include-ads')
612 # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future
613 # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it
616 opts
.recodevideo
if opts
.recodevideo
in FFmpegVideoConvertorPP
.SUPPORTED_EXTS
617 else opts
.remuxvideo
if opts
.remuxvideo
in FFmpegVideoRemuxerPP
.SUPPORTED_EXTS
618 else opts
.audioformat
if (opts
.extractaudio
and opts
.audioformat
!= 'best')
622 None if opts
.match_filter
is None
623 else match_filter_func(opts
.match_filter
))
626 'usenetrc': opts
.usenetrc
,
627 'netrc_location': opts
.netrc_location
,
628 'username': opts
.username
,
629 'password': opts
.password
,
630 'twofactor': opts
.twofactor
,
631 'videopassword': opts
.videopassword
,
632 'ap_mso': opts
.ap_mso
,
633 'ap_username': opts
.ap_username
,
634 'ap_password': opts
.ap_password
,
635 'quiet': (opts
.quiet
or any_getting
or any_printing
),
636 'no_warnings': opts
.no_warnings
,
637 'forceurl': opts
.geturl
,
638 'forcetitle': opts
.gettitle
,
639 'forceid': opts
.getid
,
640 'forcethumbnail': opts
.getthumbnail
,
641 'forcedescription': opts
.getdescription
,
642 'forceduration': opts
.getduration
,
643 'forcefilename': opts
.getfilename
,
644 'forceformat': opts
.getformat
,
645 'forceprint': opts
.forceprint
,
646 'forcejson': opts
.dumpjson
or opts
.print_json
,
647 'dump_single_json': opts
.dump_single_json
,
648 'force_write_download_archive': opts
.force_write_download_archive
,
649 'simulate': (any_getting
or None) if opts
.simulate
is None else opts
.simulate
,
650 'skip_download': opts
.skip_download
,
651 'format': opts
.format
,
652 'allow_unplayable_formats': opts
.allow_unplayable_formats
,
653 'ignore_no_formats_error': opts
.ignore_no_formats_error
,
654 'format_sort': opts
.format_sort
,
655 'format_sort_force': opts
.format_sort_force
,
656 'allow_multiple_video_streams': opts
.allow_multiple_video_streams
,
657 'allow_multiple_audio_streams': opts
.allow_multiple_audio_streams
,
658 'check_formats': opts
.check_formats
,
659 'listformats': opts
.listformats
,
660 'listformats_table': opts
.listformats_table
,
661 'outtmpl': opts
.outtmpl
,
662 'outtmpl_na_placeholder': opts
.outtmpl_na_placeholder
,
664 'autonumber_size': opts
.autonumber_size
,
665 'autonumber_start': opts
.autonumber_start
,
666 'restrictfilenames': opts
.restrictfilenames
,
667 'windowsfilenames': opts
.windowsfilenames
,
668 'ignoreerrors': opts
.ignoreerrors
,
669 'force_generic_extractor': opts
.force_generic_extractor
,
670 'ratelimit': opts
.ratelimit
,
671 'throttledratelimit': opts
.throttledratelimit
,
672 'overwrites': opts
.overwrites
,
673 'retries': opts
.retries
,
674 'fragment_retries': opts
.fragment_retries
,
675 'extractor_retries': opts
.extractor_retries
,
676 'skip_unavailable_fragments': opts
.skip_unavailable_fragments
,
677 'keep_fragments': opts
.keep_fragments
,
678 'concurrent_fragment_downloads': opts
.concurrent_fragment_downloads
,
679 'buffersize': opts
.buffersize
,
680 'noresizebuffer': opts
.noresizebuffer
,
681 'http_chunk_size': opts
.http_chunk_size
,
682 'continuedl': opts
.continue_dl
,
683 'noprogress': opts
.quiet
if opts
.noprogress
is None else opts
.noprogress
,
684 'progress_with_newline': opts
.progress_with_newline
,
685 'progress_template': opts
.progress_template
,
686 'playliststart': opts
.playliststart
,
687 'playlistend': opts
.playlistend
,
688 'playlistreverse': opts
.playlist_reverse
,
689 'playlistrandom': opts
.playlist_random
,
690 'noplaylist': opts
.noplaylist
,
691 'logtostderr': outtmpl_default
== '-',
692 'consoletitle': opts
.consoletitle
,
693 'nopart': opts
.nopart
,
694 'updatetime': opts
.updatetime
,
695 'writedescription': opts
.writedescription
,
696 'writeannotations': opts
.writeannotations
,
697 'writeinfojson': opts
.writeinfojson
,
698 'allow_playlist_files': opts
.allow_playlist_files
,
699 'clean_infojson': opts
.clean_infojson
,
700 'getcomments': opts
.getcomments
,
701 'writethumbnail': opts
.writethumbnail
is True,
702 'write_all_thumbnails': opts
.writethumbnail
== 'all',
703 'writelink': opts
.writelink
,
704 'writeurllink': opts
.writeurllink
,
705 'writewebloclink': opts
.writewebloclink
,
706 'writedesktoplink': opts
.writedesktoplink
,
707 'writesubtitles': opts
.writesubtitles
,
708 'writeautomaticsub': opts
.writeautomaticsub
,
709 'allsubtitles': opts
.allsubtitles
,
710 'listsubtitles': opts
.listsubtitles
,
711 'subtitlesformat': opts
.subtitlesformat
,
712 'subtitleslangs': opts
.subtitleslangs
,
713 'matchtitle': decodeOption(opts
.matchtitle
),
714 'rejecttitle': decodeOption(opts
.rejecttitle
),
715 'max_downloads': opts
.max_downloads
,
716 'prefer_free_formats': opts
.prefer_free_formats
,
717 'trim_file_name': opts
.trim_file_name
,
718 'verbose': opts
.verbose
,
719 'dump_intermediate_pages': opts
.dump_intermediate_pages
,
720 'write_pages': opts
.write_pages
,
722 'keepvideo': opts
.keepvideo
,
723 'min_filesize': opts
.min_filesize
,
724 'max_filesize': opts
.max_filesize
,
725 'min_views': opts
.min_views
,
726 'max_views': opts
.max_views
,
728 'cachedir': opts
.cachedir
,
729 'youtube_print_sig_code': opts
.youtube_print_sig_code
,
730 'age_limit': opts
.age_limit
,
731 'download_archive': download_archive_fn
,
732 'break_on_existing': opts
.break_on_existing
,
733 'break_on_reject': opts
.break_on_reject
,
734 'break_per_url': opts
.break_per_url
,
735 'skip_playlist_after_errors': opts
.skip_playlist_after_errors
,
736 'cookiefile': opts
.cookiefile
,
737 'cookiesfrombrowser': opts
.cookiesfrombrowser
,
738 'nocheckcertificate': opts
.no_check_certificate
,
739 'prefer_insecure': opts
.prefer_insecure
,
741 'socket_timeout': opts
.socket_timeout
,
742 'bidi_workaround': opts
.bidi_workaround
,
743 'debug_printtraffic': opts
.debug_printtraffic
,
744 'prefer_ffmpeg': opts
.prefer_ffmpeg
,
745 'include_ads': opts
.include_ads
,
746 'default_search': opts
.default_search
,
747 'dynamic_mpd': opts
.dynamic_mpd
,
748 'extractor_args': opts
.extractor_args
,
749 'youtube_include_dash_manifest': opts
.youtube_include_dash_manifest
,
750 'youtube_include_hls_manifest': opts
.youtube_include_hls_manifest
,
751 'encoding': opts
.encoding
,
752 'extract_flat': opts
.extract_flat
,
753 'live_from_start': opts
.live_from_start
,
754 'wait_for_video': opts
.wait_for_video
,
755 'mark_watched': opts
.mark_watched
,
756 'merge_output_format': opts
.merge_output_format
,
757 'final_ext': final_ext
,
758 'postprocessors': postprocessors
,
760 'source_address': opts
.source_address
,
761 'call_home': opts
.call_home
,
762 'sleep_interval_requests': opts
.sleep_interval_requests
,
763 'sleep_interval': opts
.sleep_interval
,
764 'max_sleep_interval': opts
.max_sleep_interval
,
765 'sleep_interval_subtitles': opts
.sleep_interval_subtitles
,
766 'external_downloader': opts
.external_downloader
,
767 'list_thumbnails': opts
.list_thumbnails
,
768 'playlist_items': opts
.playlist_items
,
769 'xattr_set_filesize': opts
.xattr_set_filesize
,
770 'match_filter': match_filter
,
771 'no_color': opts
.no_color
,
772 'ffmpeg_location': opts
.ffmpeg_location
,
773 'hls_prefer_native': opts
.hls_prefer_native
,
774 'hls_use_mpegts': opts
.hls_use_mpegts
,
775 'hls_split_discontinuity': opts
.hls_split_discontinuity
,
776 'external_downloader_args': opts
.external_downloader_args
,
777 'postprocessor_args': opts
.postprocessor_args
,
778 'cn_verification_proxy': opts
.cn_verification_proxy
,
779 'geo_verification_proxy': opts
.geo_verification_proxy
,
780 'geo_bypass': opts
.geo_bypass
,
781 'geo_bypass_country': opts
.geo_bypass_country
,
782 'geo_bypass_ip_block': opts
.geo_bypass_ip_block
,
783 '_warnings': warnings
,
784 '_deprecation_warnings': deprecation_warnings
,
785 'compat_opts': compat_opts
,
788 with YoutubeDL(ydl_opts
) as ydl
:
789 actual_use
= all_urls
or opts
.load_info_filename
797 # If updater returns True, exit. Required for windows
800 sys
.exit('ERROR: The program must exit for the update to complete')
805 if opts
.update_self
or opts
.rm_cachedir
:
808 ydl
.warn_if_short_id(sys
.argv
[1:] if argv
is None else argv
)
810 'You must provide at least one URL.\n'
811 'Type yt-dlp --help to see a list of all options.')
814 if opts
.load_info_filename
is not None:
815 retcode
= ydl
.download_with_info_file(expand_path(opts
.load_info_filename
))
817 retcode
= ydl
.download(all_urls
)
818 except DownloadCancelled
:
819 ydl
.to_screen('Aborting remaining downloads')
828 except DownloadError
:
830 except SameFileError
as e
:
831 sys
.exit(f
'ERROR: {e}')
832 except KeyboardInterrupt:
833 sys
.exit('\nERROR: Interrupted by user')
834 except BrokenPipeError
as e
:
835 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
836 devnull
= os
.open(os
.devnull
, os
.O_WRONLY
)
837 os
.dup2(devnull
, sys
.stdout
.fileno())
838 sys
.exit(f
'\nERROR: {e}')
841 __all__
= ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']