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
46 from .update
import run_update
47 from .downloader
import (
50 from .extractor
import gen_extractors
, list_extractors
51 from .extractor
.common
import InfoExtractor
52 from .extractor
.adobepass
import MSO_INFO
53 from .postprocessor
import (
55 FFmpegSubtitlesConvertorPP
,
56 FFmpegThumbnailsConvertorPP
,
57 FFmpegVideoConvertorPP
,
62 from .YoutubeDL
import YoutubeDL
65 def _real_main(argv
=None):
66 # Compatibility fixes for Windows
67 if sys
.platform
== 'win32':
68 # https://github.com/ytdl-org/youtube-dl/issues/820
69 codecs
.register(lambda name
: codecs
.lookup('utf-8') if name
== 'cp65001' else None)
71 workaround_optparse_bug9161()
73 setproctitle('yt-dlp')
75 parser
, opts
, args
= parseOpts(argv
)
79 if opts
.user_agent
is not None:
80 std_headers
['User-Agent'] = opts
.user_agent
83 if opts
.referer
is not None:
84 std_headers
['Referer'] = opts
.referer
87 std_headers
.update(opts
.headers
)
90 if opts
.dump_user_agent
:
91 write_string(std_headers
['User-Agent'] + '\n', out
=sys
.stdout
)
94 # Batch file verification
96 if opts
.batchfile
is not None:
98 if opts
.batchfile
== '-':
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
.usenetrc
and (opts
.username
is not None or opts
.password
is not None):
140 parser
.error('using .netrc conflicts with giving username/password')
141 if opts
.password
is not None and opts
.username
is None:
142 parser
.error('account username missing\n')
143 if opts
.ap_password
is not None and opts
.ap_username
is None:
144 parser
.error('TV Provider account username missing\n')
145 if opts
.autonumber_size
is not None:
146 if opts
.autonumber_size
<= 0:
147 parser
.error('auto number size must be positive')
148 if opts
.autonumber_start
is not None:
149 if opts
.autonumber_start
< 0:
150 parser
.error('auto number start must be positive or 0')
151 if opts
.username
is not None and opts
.password
is None:
152 opts
.password
= compat_getpass('Type account password and press [Return]: ')
153 if opts
.ap_username
is not None and opts
.ap_password
is None:
154 opts
.ap_password
= compat_getpass('Type TV provider account password and press [Return]: ')
155 if opts
.ratelimit
is not None:
156 numeric_limit
= FileDownloader
.parse_bytes(opts
.ratelimit
)
157 if numeric_limit
is None:
158 parser
.error('invalid rate limit specified')
159 opts
.ratelimit
= numeric_limit
160 if opts
.throttledratelimit
is not None:
161 numeric_limit
= FileDownloader
.parse_bytes(opts
.throttledratelimit
)
162 if numeric_limit
is None:
163 parser
.error('invalid rate limit specified')
164 opts
.throttledratelimit
= numeric_limit
165 if opts
.min_filesize
is not None:
166 numeric_limit
= FileDownloader
.parse_bytes(opts
.min_filesize
)
167 if numeric_limit
is None:
168 parser
.error('invalid min_filesize specified')
169 opts
.min_filesize
= numeric_limit
170 if opts
.max_filesize
is not None:
171 numeric_limit
= FileDownloader
.parse_bytes(opts
.max_filesize
)
172 if numeric_limit
is None:
173 parser
.error('invalid max_filesize specified')
174 opts
.max_filesize
= numeric_limit
175 if opts
.sleep_interval
is not None:
176 if opts
.sleep_interval
< 0:
177 parser
.error('sleep interval must be positive or 0')
178 if opts
.max_sleep_interval
is not None:
179 if opts
.max_sleep_interval
< 0:
180 parser
.error('max sleep interval must be positive or 0')
181 if opts
.sleep_interval
is None:
182 parser
.error('min sleep interval must be specified, use --min-sleep-interval')
183 if opts
.max_sleep_interval
< opts
.sleep_interval
:
184 parser
.error('max sleep interval must be greater than or equal to min sleep interval')
186 opts
.max_sleep_interval
= opts
.sleep_interval
187 if opts
.sleep_interval_subtitles
is not None:
188 if opts
.sleep_interval_subtitles
< 0:
189 parser
.error('subtitles sleep interval must be positive or 0')
190 if opts
.sleep_interval_requests
is not None:
191 if opts
.sleep_interval_requests
< 0:
192 parser
.error('requests sleep interval must be positive or 0')
193 if opts
.ap_mso
and opts
.ap_mso
not in MSO_INFO
:
194 parser
.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
195 if opts
.overwrites
: # --yes-overwrites implies --no-continue
196 opts
.continue_dl
= False
197 if opts
.concurrent_fragment_downloads
<= 0:
198 raise ValueError('Concurrent fragments must be positive')
200 def parse_retries(retries
, name
=''):
201 if retries
in ('inf', 'infinite'):
202 parsed_retries
= float('inf')
205 parsed_retries
= int(retries
)
206 except (TypeError, ValueError):
207 parser
.error('invalid %sretry count specified' % name
)
208 return parsed_retries
209 if opts
.retries
is not None:
210 opts
.retries
= parse_retries(opts
.retries
)
211 if opts
.fragment_retries
is not None:
212 opts
.fragment_retries
= parse_retries(opts
.fragment_retries
, 'fragment ')
213 if opts
.extractor_retries
is not None:
214 opts
.extractor_retries
= parse_retries(opts
.extractor_retries
, 'extractor ')
215 if opts
.buffersize
is not None:
216 numeric_buffersize
= FileDownloader
.parse_bytes(opts
.buffersize
)
217 if numeric_buffersize
is None:
218 parser
.error('invalid buffer size specified')
219 opts
.buffersize
= numeric_buffersize
220 if opts
.http_chunk_size
is not None:
221 numeric_chunksize
= FileDownloader
.parse_bytes(opts
.http_chunk_size
)
222 if not numeric_chunksize
:
223 parser
.error('invalid http chunk size specified')
224 opts
.http_chunk_size
= numeric_chunksize
225 if opts
.playliststart
<= 0:
226 raise ValueError('Playlist start must be positive')
227 if opts
.playlistend
not in (-1, None) and opts
.playlistend
< opts
.playliststart
:
228 raise ValueError('Playlist end must be greater than playlist start')
229 if opts
.extractaudio
:
230 opts
.audioformat
= opts
.audioformat
.lower()
231 if opts
.audioformat
not in ['best'] + list(FFmpegExtractAudioPP
.SUPPORTED_EXTS
):
232 parser
.error('invalid audio format specified')
233 if opts
.audioquality
:
234 opts
.audioquality
= opts
.audioquality
.strip('k').strip('K')
235 audioquality
= int_or_none(float_or_none(opts
.audioquality
)) # int_or_none prevents inf, nan
236 if audioquality
is None or audioquality
< 0:
237 parser
.error('invalid audio quality specified')
238 if opts
.recodevideo
is not None:
239 opts
.recodevideo
= opts
.recodevideo
.replace(' ', '')
240 if not re
.match(FFmpegVideoConvertorPP
.FORMAT_RE
, opts
.recodevideo
):
241 parser
.error('invalid video remux format specified')
242 if opts
.remuxvideo
is not None:
243 opts
.remuxvideo
= opts
.remuxvideo
.replace(' ', '')
244 if not re
.match(FFmpegVideoRemuxerPP
.FORMAT_RE
, opts
.remuxvideo
):
245 parser
.error('invalid video remux format specified')
246 if opts
.convertsubtitles
is not None:
247 if opts
.convertsubtitles
not in FFmpegSubtitlesConvertorPP
.SUPPORTED_EXTS
:
248 parser
.error('invalid subtitle format specified')
249 if opts
.convertthumbnails
is not None:
250 if opts
.convertthumbnails
not in FFmpegThumbnailsConvertorPP
.SUPPORTED_EXTS
:
251 parser
.error('invalid thumbnail format specified')
253 if opts
.cookiesfrombrowser
is not None:
254 opts
.cookiesfrombrowser
= [
255 part
.strip() or None for part
in opts
.cookiesfrombrowser
.split(':', 1)]
256 if opts
.cookiesfrombrowser
[0].lower() not in SUPPORTED_BROWSERS
:
257 parser
.error('unsupported browser specified for cookies')
259 if opts
.date
is not None:
260 date
= DateRange
.day(opts
.date
)
262 date
= DateRange(opts
.dateafter
, opts
.datebefore
)
264 compat_opts
= opts
.compat_opts
266 def report_conflict(arg1
, arg2
):
267 warnings
.append(f
'{arg2} is ignored since {arg1} was given')
269 def _unused_compat_opt(name
):
270 if name
not in compat_opts
:
272 compat_opts
.discard(name
)
273 compat_opts
.update(['*%s' % name
])
276 def set_default_compat(compat_name
, opt_name
, default
=True, remove_compat
=True):
277 attr
= getattr(opts
, opt_name
)
278 if compat_name
in compat_opts
:
280 setattr(opts
, opt_name
, not default
)
284 _unused_compat_opt(compat_name
)
287 setattr(opts
, opt_name
, default
)
290 set_default_compat('abort-on-error', 'ignoreerrors', 'only_download')
291 set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
292 set_default_compat('no-clean-infojson', 'clean_infojson')
293 if 'no-attach-info-json' in compat_opts
:
294 if opts
.embed_infojson
:
295 _unused_compat_opt('no-attach-info-json')
297 opts
.embed_infojson
= False
298 if 'format-sort' in compat_opts
:
299 opts
.format_sort
.extend(InfoExtractor
.FormatSort
.ytdl_default
)
300 _video_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat
=False)
301 _audio_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat
=False)
302 if _video_multistreams_set
is False and _audio_multistreams_set
is False:
303 _unused_compat_opt('multistreams')
304 outtmpl_default
= opts
.outtmpl
.get('default')
306 if outtmpl_default
is None:
307 outtmpl_default
= opts
.outtmpl
['default'] = '%(id)s.%(ext)s'
309 report_conflict('--output', '--id')
310 if 'filename' in compat_opts
:
311 if outtmpl_default
is None:
312 outtmpl_default
= opts
.outtmpl
['default'] = '%(title)s-%(id)s.%(ext)s'
314 _unused_compat_opt('filename')
316 def validate_outtmpl(tmpl
, msg
):
317 err
= YoutubeDL
.validate_outtmpl(tmpl
)
319 parser
.error('invalid %s %r: %s' % (msg
, tmpl
, error_to_compat_str(err
)))
321 for k
, tmpl
in opts
.outtmpl
.items():
322 validate_outtmpl(tmpl
, f
'{k} output template')
323 opts
.forceprint
= opts
.forceprint
or []
324 for tmpl
in opts
.forceprint
or []:
325 validate_outtmpl(tmpl
, 'print template')
326 validate_outtmpl(opts
.sponsorblock_chapter_title
, 'SponsorBlock chapter title')
327 for k
, tmpl
in opts
.progress_template
.items():
328 k
= f
'{k[:-6]} console title' if '-title' in k
else f
'{k} progress'
329 validate_outtmpl(tmpl
, f
'{k} template')
331 if opts
.extractaudio
and not opts
.keepvideo
and opts
.format
is None:
332 opts
.format
= 'bestaudio/best'
334 if outtmpl_default
is not None and not os
.path
.splitext(outtmpl_default
)[1] and opts
.extractaudio
:
335 parser
.error('Cannot download a video and extract audio into the same'
336 ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
337 ' template'.format(outtmpl_default
))
339 for f
in opts
.format_sort
:
340 if re
.match(InfoExtractor
.FormatSort
.regex
, f
) is None:
341 parser
.error('invalid format sort string "%s" specified' % f
)
343 def metadataparser_actions(f
):
344 if isinstance(f
, str):
345 cmd
= '--parse-metadata %s' % compat_shlex_quote(f
)
347 actions
= [MetadataFromFieldPP
.to_action(f
)]
348 except Exception as err
:
349 parser
.error(f
'{cmd} is invalid; {err}')
351 cmd
= '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote
, f
))
352 actions
= ((MetadataParserPP
.Actions
.REPLACE
, x
, *f
[1:]) for x
in f
[0].split(','))
354 for action
in actions
:
356 MetadataParserPP
.validate_action(*action
)
357 except Exception as err
:
358 parser
.error(f
'{cmd} is invalid; {err}')
361 if opts
.parse_metadata
is None:
362 opts
.parse_metadata
= []
363 if opts
.metafromtitle
is not None:
364 opts
.parse_metadata
.append('title:%s' % opts
.metafromtitle
)
365 opts
.parse_metadata
= list(itertools
.chain(*map(metadataparser_actions
, opts
.parse_metadata
)))
367 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
368 any_printing
= opts
.print_json
369 download_archive_fn
= expand_path(opts
.download_archive
) if opts
.download_archive
is not None else opts
.download_archive
371 # If JSON is not printed anywhere, but comments are requested, save it to file
372 printing_json
= opts
.dumpjson
or opts
.print_json
or opts
.dump_single_json
373 if opts
.getcomments
and not printing_json
:
374 opts
.writeinfojson
= True
376 if opts
.no_sponsorblock
:
377 opts
.sponsorblock_mark
= set()
378 opts
.sponsorblock_remove
= set()
379 sponsorblock_query
= opts
.sponsorblock_mark | opts
.sponsorblock_remove
381 opts
.remove_chapters
= opts
.remove_chapters
or []
383 if (opts
.remove_chapters
or sponsorblock_query
) and opts
.sponskrub
is not False:
385 if opts
.remove_chapters
:
386 report_conflict('--remove-chapters', '--sponskrub')
387 if opts
.sponsorblock_mark
:
388 report_conflict('--sponsorblock-mark', '--sponskrub')
389 if opts
.sponsorblock_remove
:
390 report_conflict('--sponsorblock-remove', '--sponskrub')
391 opts
.sponskrub
= False
392 if opts
.sponskrub_cut
and opts
.split_chapters
and opts
.sponskrub
is not False:
393 report_conflict('--split-chapter', '--sponskrub-cut')
394 opts
.sponskrub_cut
= False
396 if opts
.remuxvideo
and opts
.recodevideo
:
397 report_conflict('--recode-video', '--remux-video')
398 opts
.remuxvideo
= False
400 if opts
.allow_unplayable_formats
:
401 def report_unplayable_conflict(opt_name
, arg
, default
=False, allowed
=None):
402 val
= getattr(opts
, opt_name
)
403 if (not allowed
and val
) or not allowed(val
):
404 report_conflict('--allow-unplayable-formats', arg
)
405 setattr(opts
, opt_name
, default
)
407 report_unplayable_conflict('extractaudio', '--extract-audio')
408 report_unplayable_conflict('remuxvideo', '--remux-video')
409 report_unplayable_conflict('recodevideo', '--recode-video')
410 report_unplayable_conflict('addmetadata', '--embed-metadata')
411 report_unplayable_conflict('addchapters', '--embed-chapters')
412 report_unplayable_conflict('embed_infojson', '--embed-info-json')
413 opts
.embed_infojson
= False
414 report_unplayable_conflict('embedsubtitles', '--embed-subs')
415 report_unplayable_conflict('embedthumbnail', '--embed-thumbnail')
416 report_unplayable_conflict('xattrs', '--xattrs')
417 report_unplayable_conflict('fixup', '--fixup', default
='never', allowed
=lambda x
: x
in (None, 'never', 'ignore'))
419 report_unplayable_conflict('remove_chapters', '--remove-chapters', default
=[])
420 report_unplayable_conflict('sponsorblock_remove', '--sponsorblock-remove', default
=set())
421 report_unplayable_conflict('sponskrub', '--sponskrub', default
=set())
422 opts
.sponskrub
= False
424 if (opts
.addmetadata
or opts
.sponsorblock_mark
) and opts
.addchapters
is None:
425 opts
.addchapters
= True
428 postprocessors
= list(opts
.add_postprocessors
)
429 if sponsorblock_query
:
430 postprocessors
.append({
431 'key': 'SponsorBlock',
432 'categories': sponsorblock_query
,
433 'api': opts
.sponsorblock_api
,
434 # Run this immediately after extraction is complete
435 'when': 'pre_process'
437 if opts
.parse_metadata
:
438 postprocessors
.append({
439 'key': 'MetadataParser',
440 'actions': opts
.parse_metadata
,
441 # Run this immediately after extraction is complete
442 'when': 'pre_process'
444 if opts
.convertsubtitles
:
445 postprocessors
.append({
446 'key': 'FFmpegSubtitlesConvertor',
447 'format': opts
.convertsubtitles
,
448 # Run this before the actual video download
451 if opts
.convertthumbnails
:
452 postprocessors
.append({
453 'key': 'FFmpegThumbnailsConvertor',
454 'format': opts
.convertthumbnails
,
455 # Run this before the actual video download
458 # Must be after all other before_dl
459 if opts
.exec_before_dl_cmd
:
460 postprocessors
.append({
462 'exec_cmd': opts
.exec_before_dl_cmd
,
465 if opts
.extractaudio
:
466 postprocessors
.append({
467 'key': 'FFmpegExtractAudio',
468 'preferredcodec': opts
.audioformat
,
469 'preferredquality': opts
.audioquality
,
470 'nopostoverwrites': opts
.nopostoverwrites
,
473 postprocessors
.append({
474 'key': 'FFmpegVideoRemuxer',
475 'preferedformat': opts
.remuxvideo
,
478 postprocessors
.append({
479 'key': 'FFmpegVideoConvertor',
480 'preferedformat': opts
.recodevideo
,
482 # If ModifyChapters is going to remove chapters, subtitles must already be in the container.
483 if opts
.embedsubtitles
:
484 already_have_subtitle
= opts
.writesubtitles
and 'no-keep-subs' not in compat_opts
485 postprocessors
.append({
486 'key': 'FFmpegEmbedSubtitle',
487 # already_have_subtitle = True prevents the file from being deleted after embedding
488 'already_have_subtitle': already_have_subtitle
490 if not opts
.writeautomaticsub
and 'no-keep-subs' not in compat_opts
:
491 opts
.writesubtitles
= True
492 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
493 # this was the old behaviour if only --all-sub was given.
494 if opts
.allsubtitles
and not opts
.writeautomaticsub
:
495 opts
.writesubtitles
= True
496 # ModifyChapters must run before FFmpegMetadataPP
497 remove_chapters_patterns
, remove_ranges
= [], []
498 for regex
in opts
.remove_chapters
:
499 if regex
.startswith('*'):
500 dur
= list(map(parse_duration
, regex
[1:].split('-')))
501 if len(dur
) == 2 and all(t
is not None for t
in dur
):
502 remove_ranges
.append(tuple(dur
))
504 parser
.error(f
'invalid --remove-chapters time range {regex!r}. Must be of the form ?start-end')
506 remove_chapters_patterns
.append(re
.compile(regex
))
507 except re
.error
as err
:
508 parser
.error(f
'invalid --remove-chapters regex {regex!r} - {err}')
509 if opts
.remove_chapters
or sponsorblock_query
:
510 postprocessors
.append({
511 'key': 'ModifyChapters',
512 'remove_chapters_patterns': remove_chapters_patterns
,
513 'remove_sponsor_segments': opts
.sponsorblock_remove
,
514 'remove_ranges': remove_ranges
,
515 'sponsorblock_chapter_title': opts
.sponsorblock_chapter_title
,
516 'force_keyframes': opts
.force_keyframes_at_cuts
518 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
519 # FFmpegExtractAudioPP as containers before conversion may not support
520 # metadata (3gp, webm, etc.)
521 # By default ffmpeg preserves metadata applicable for both
522 # source and target containers. From this point the container won't change,
523 # so metadata can be added here.
524 if opts
.addmetadata
or opts
.addchapters
or opts
.embed_infojson
:
525 if opts
.embed_infojson
is None:
526 opts
.embed_infojson
= 'if_exists'
527 postprocessors
.append({
528 'key': 'FFmpegMetadata',
529 'add_chapters': opts
.addchapters
,
530 'add_metadata': opts
.addmetadata
,
531 'add_infojson': opts
.embed_infojson
,
534 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
535 # but must be below EmbedSubtitle and FFmpegMetadata
536 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
537 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
538 if opts
.sponskrub
is not False:
539 postprocessors
.append({
541 'path': opts
.sponskrub_path
,
542 'args': opts
.sponskrub_args
,
543 'cut': opts
.sponskrub_cut
,
544 'force': opts
.sponskrub_force
,
545 'ignoreerror': opts
.sponskrub
is None,
547 if opts
.embedthumbnail
:
548 already_have_thumbnail
= opts
.writethumbnail
or opts
.write_all_thumbnails
549 postprocessors
.append({
550 'key': 'EmbedThumbnail',
551 # already_have_thumbnail = True prevents the file from being deleted after embedding
552 'already_have_thumbnail': already_have_thumbnail
554 if not already_have_thumbnail
:
555 opts
.writethumbnail
= True
556 opts
.outtmpl
['pl_thumbnail'] = ''
557 if opts
.split_chapters
:
558 postprocessors
.append({
559 'key': 'FFmpegSplitChapters',
560 'force_keyframes': opts
.force_keyframes_at_cuts
,
562 # XAttrMetadataPP should be run after post-processors that may change file contents
564 postprocessors
.append({'key': 'XAttrMetadata'}
)
565 # Exec must be the last PP
567 postprocessors
.append({
569 'exec_cmd': opts
.exec_cmd
,
570 # Run this only after the files have been moved to their final locations
574 def report_args_compat(arg
, name
):
575 warnings
.append('%s given without specifying name. The arguments will be given to all %s' % (arg
, name
))
577 if 'default' in opts
.external_downloader_args
:
578 report_args_compat('--downloader-args', 'external downloaders')
580 if 'default-compat' in opts
.postprocessor_args
and 'default' not in opts
.postprocessor_args
:
581 report_args_compat('--post-processor-args', 'post-processors')
582 opts
.postprocessor_args
.setdefault('sponskrub', [])
583 opts
.postprocessor_args
['default'] = opts
.postprocessor_args
['default-compat']
586 opts
.recodevideo
if opts
.recodevideo
in FFmpegVideoConvertorPP
.SUPPORTED_EXTS
587 else opts
.remuxvideo
if opts
.remuxvideo
in FFmpegVideoRemuxerPP
.SUPPORTED_EXTS
588 else opts
.audioformat
if (opts
.extractaudio
and opts
.audioformat
!= 'best')
592 None if opts
.match_filter
is None
593 else match_filter_func(opts
.match_filter
))
596 'usenetrc': opts
.usenetrc
,
597 'netrc_location': opts
.netrc_location
,
598 'username': opts
.username
,
599 'password': opts
.password
,
600 'twofactor': opts
.twofactor
,
601 'videopassword': opts
.videopassword
,
602 'ap_mso': opts
.ap_mso
,
603 'ap_username': opts
.ap_username
,
604 'ap_password': opts
.ap_password
,
605 'quiet': (opts
.quiet
or any_getting
or any_printing
),
606 'no_warnings': opts
.no_warnings
,
607 'forceurl': opts
.geturl
,
608 'forcetitle': opts
.gettitle
,
609 'forceid': opts
.getid
,
610 'forcethumbnail': opts
.getthumbnail
,
611 'forcedescription': opts
.getdescription
,
612 'forceduration': opts
.getduration
,
613 'forcefilename': opts
.getfilename
,
614 'forceformat': opts
.getformat
,
615 'forceprint': opts
.forceprint
,
616 'forcejson': opts
.dumpjson
or opts
.print_json
,
617 'dump_single_json': opts
.dump_single_json
,
618 'force_write_download_archive': opts
.force_write_download_archive
,
619 'simulate': (any_getting
or None) if opts
.simulate
is None else opts
.simulate
,
620 'skip_download': opts
.skip_download
,
621 'format': opts
.format
,
622 'allow_unplayable_formats': opts
.allow_unplayable_formats
,
623 'ignore_no_formats_error': opts
.ignore_no_formats_error
,
624 'format_sort': opts
.format_sort
,
625 'format_sort_force': opts
.format_sort_force
,
626 'allow_multiple_video_streams': opts
.allow_multiple_video_streams
,
627 'allow_multiple_audio_streams': opts
.allow_multiple_audio_streams
,
628 'check_formats': opts
.check_formats
,
629 'listformats': opts
.listformats
,
630 'listformats_table': opts
.listformats_table
,
631 'outtmpl': opts
.outtmpl
,
632 'outtmpl_na_placeholder': opts
.outtmpl_na_placeholder
,
634 'autonumber_size': opts
.autonumber_size
,
635 'autonumber_start': opts
.autonumber_start
,
636 'restrictfilenames': opts
.restrictfilenames
,
637 'windowsfilenames': opts
.windowsfilenames
,
638 'ignoreerrors': opts
.ignoreerrors
,
639 'force_generic_extractor': opts
.force_generic_extractor
,
640 'ratelimit': opts
.ratelimit
,
641 'throttledratelimit': opts
.throttledratelimit
,
642 'overwrites': opts
.overwrites
,
643 'retries': opts
.retries
,
644 'fragment_retries': opts
.fragment_retries
,
645 'extractor_retries': opts
.extractor_retries
,
646 'skip_unavailable_fragments': opts
.skip_unavailable_fragments
,
647 'keep_fragments': opts
.keep_fragments
,
648 'concurrent_fragment_downloads': opts
.concurrent_fragment_downloads
,
649 'buffersize': opts
.buffersize
,
650 'noresizebuffer': opts
.noresizebuffer
,
651 'http_chunk_size': opts
.http_chunk_size
,
652 'continuedl': opts
.continue_dl
,
653 'noprogress': opts
.quiet
if opts
.noprogress
is None else opts
.noprogress
,
654 'progress_with_newline': opts
.progress_with_newline
,
655 'progress_template': opts
.progress_template
,
656 'playliststart': opts
.playliststart
,
657 'playlistend': opts
.playlistend
,
658 'playlistreverse': opts
.playlist_reverse
,
659 'playlistrandom': opts
.playlist_random
,
660 'noplaylist': opts
.noplaylist
,
661 'logtostderr': outtmpl_default
== '-',
662 'consoletitle': opts
.consoletitle
,
663 'nopart': opts
.nopart
,
664 'updatetime': opts
.updatetime
,
665 'writedescription': opts
.writedescription
,
666 'writeannotations': opts
.writeannotations
,
667 'writeinfojson': opts
.writeinfojson
,
668 'allow_playlist_files': opts
.allow_playlist_files
,
669 'clean_infojson': opts
.clean_infojson
,
670 'getcomments': opts
.getcomments
,
671 'writethumbnail': opts
.writethumbnail
,
672 'write_all_thumbnails': opts
.write_all_thumbnails
,
673 'writelink': opts
.writelink
,
674 'writeurllink': opts
.writeurllink
,
675 'writewebloclink': opts
.writewebloclink
,
676 'writedesktoplink': opts
.writedesktoplink
,
677 'writesubtitles': opts
.writesubtitles
,
678 'writeautomaticsub': opts
.writeautomaticsub
,
679 'allsubtitles': opts
.allsubtitles
,
680 'listsubtitles': opts
.listsubtitles
,
681 'subtitlesformat': opts
.subtitlesformat
,
682 'subtitleslangs': opts
.subtitleslangs
,
683 'matchtitle': decodeOption(opts
.matchtitle
),
684 'rejecttitle': decodeOption(opts
.rejecttitle
),
685 'max_downloads': opts
.max_downloads
,
686 'prefer_free_formats': opts
.prefer_free_formats
,
687 'trim_file_name': opts
.trim_file_name
,
688 'verbose': opts
.verbose
,
689 'dump_intermediate_pages': opts
.dump_intermediate_pages
,
690 'write_pages': opts
.write_pages
,
692 'keepvideo': opts
.keepvideo
,
693 'min_filesize': opts
.min_filesize
,
694 'max_filesize': opts
.max_filesize
,
695 'min_views': opts
.min_views
,
696 'max_views': opts
.max_views
,
698 'cachedir': opts
.cachedir
,
699 'youtube_print_sig_code': opts
.youtube_print_sig_code
,
700 'age_limit': opts
.age_limit
,
701 'download_archive': download_archive_fn
,
702 'break_on_existing': opts
.break_on_existing
,
703 'break_on_reject': opts
.break_on_reject
,
704 'skip_playlist_after_errors': opts
.skip_playlist_after_errors
,
705 'cookiefile': opts
.cookiefile
,
706 'cookiesfrombrowser': opts
.cookiesfrombrowser
,
707 'nocheckcertificate': opts
.no_check_certificate
,
708 'prefer_insecure': opts
.prefer_insecure
,
710 'socket_timeout': opts
.socket_timeout
,
711 'bidi_workaround': opts
.bidi_workaround
,
712 'debug_printtraffic': opts
.debug_printtraffic
,
713 'prefer_ffmpeg': opts
.prefer_ffmpeg
,
714 'include_ads': opts
.include_ads
,
715 'default_search': opts
.default_search
,
716 'dynamic_mpd': opts
.dynamic_mpd
,
717 'extractor_args': opts
.extractor_args
,
718 'youtube_include_dash_manifest': opts
.youtube_include_dash_manifest
,
719 'youtube_include_hls_manifest': opts
.youtube_include_hls_manifest
,
720 'encoding': opts
.encoding
,
721 'extract_flat': opts
.extract_flat
,
722 'mark_watched': opts
.mark_watched
,
723 'merge_output_format': opts
.merge_output_format
,
724 'final_ext': final_ext
,
725 'postprocessors': postprocessors
,
727 'source_address': opts
.source_address
,
728 'call_home': opts
.call_home
,
729 'sleep_interval_requests': opts
.sleep_interval_requests
,
730 'sleep_interval': opts
.sleep_interval
,
731 'max_sleep_interval': opts
.max_sleep_interval
,
732 'sleep_interval_subtitles': opts
.sleep_interval_subtitles
,
733 'external_downloader': opts
.external_downloader
,
734 'list_thumbnails': opts
.list_thumbnails
,
735 'playlist_items': opts
.playlist_items
,
736 'xattr_set_filesize': opts
.xattr_set_filesize
,
737 'match_filter': match_filter
,
738 'no_color': opts
.no_color
,
739 'ffmpeg_location': opts
.ffmpeg_location
,
740 'hls_prefer_native': opts
.hls_prefer_native
,
741 'hls_use_mpegts': opts
.hls_use_mpegts
,
742 'hls_split_discontinuity': opts
.hls_split_discontinuity
,
743 'external_downloader_args': opts
.external_downloader_args
,
744 'postprocessor_args': opts
.postprocessor_args
,
745 'cn_verification_proxy': opts
.cn_verification_proxy
,
746 'geo_verification_proxy': opts
.geo_verification_proxy
,
747 'geo_bypass': opts
.geo_bypass
,
748 'geo_bypass_country': opts
.geo_bypass_country
,
749 'geo_bypass_ip_block': opts
.geo_bypass_ip_block
,
750 '_warnings': warnings
,
751 'compat_opts': compat_opts
,
754 with YoutubeDL(ydl_opts
) as ydl
:
755 actual_use
= len(all_urls
) or opts
.load_info_filename
763 # If updater returns True, exit. Required for windows
766 sys
.exit('ERROR: The program must exit for the update to complete')
771 if opts
.update_self
or opts
.rm_cachedir
:
774 ydl
.warn_if_short_id(sys
.argv
[1:] if argv
is None else argv
)
776 'You must provide at least one URL.\n'
777 'Type yt-dlp --help to see a list of all options.')
780 if opts
.load_info_filename
is not None:
781 retcode
= ydl
.download_with_info_file(expand_path(opts
.load_info_filename
))
783 retcode
= ydl
.download(all_urls
)
784 except (MaxDownloadsReached
, ExistingVideoReached
, RejectedVideoReached
):
785 ydl
.to_screen('Aborting remaining downloads')
794 except DownloadError
:
796 except SameFileError
as e
:
797 sys
.exit(f
'ERROR: {e}')
798 except KeyboardInterrupt:
799 sys
.exit('\nERROR: Interrupted by user')
800 except BrokenPipeError
as e
:
801 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
802 devnull
= os
.open(os
.devnull
, os
.O_WRONLY
)
803 os
.dup2(devnull
, sys
.stdout
.fileno())
804 sys
.exit(f
'\nERROR: {e}')
807 __all__
= ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']