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')
199 if opts
.wait_for_video
is not None:
200 mobj
= re
.match(r
'(?P<min>\d+)(?:-(?P<max>\d+))?$', opts
.wait_for_video
)
202 parser
.error('Invalid time range to wait')
203 min_wait
, max_wait
= map(int_or_none
, mobj
.group('min', 'max'))
204 if max_wait
is not None and max_wait
< min_wait
:
205 parser
.error('Invalid time range to wait')
206 opts
.wait_for_video
= (min_wait
, max_wait
)
208 def parse_retries(retries
, name
=''):
209 if retries
in ('inf', 'infinite'):
210 parsed_retries
= float('inf')
213 parsed_retries
= int(retries
)
214 except (TypeError, ValueError):
215 parser
.error('invalid %sretry count specified' % name
)
216 return parsed_retries
217 if opts
.retries
is not None:
218 opts
.retries
= parse_retries(opts
.retries
)
219 if opts
.fragment_retries
is not None:
220 opts
.fragment_retries
= parse_retries(opts
.fragment_retries
, 'fragment ')
221 if opts
.extractor_retries
is not None:
222 opts
.extractor_retries
= parse_retries(opts
.extractor_retries
, 'extractor ')
223 if opts
.buffersize
is not None:
224 numeric_buffersize
= FileDownloader
.parse_bytes(opts
.buffersize
)
225 if numeric_buffersize
is None:
226 parser
.error('invalid buffer size specified')
227 opts
.buffersize
= numeric_buffersize
228 if opts
.http_chunk_size
is not None:
229 numeric_chunksize
= FileDownloader
.parse_bytes(opts
.http_chunk_size
)
230 if not numeric_chunksize
:
231 parser
.error('invalid http chunk size specified')
232 opts
.http_chunk_size
= numeric_chunksize
233 if opts
.playliststart
<= 0:
234 raise ValueError('Playlist start must be positive')
235 if opts
.playlistend
not in (-1, None) and opts
.playlistend
< opts
.playliststart
:
236 raise ValueError('Playlist end must be greater than playlist start')
237 if opts
.extractaudio
:
238 opts
.audioformat
= opts
.audioformat
.lower()
239 if opts
.audioformat
not in ['best'] + list(FFmpegExtractAudioPP
.SUPPORTED_EXTS
):
240 parser
.error('invalid audio format specified')
241 if opts
.audioquality
:
242 opts
.audioquality
= opts
.audioquality
.strip('k').strip('K')
243 audioquality
= int_or_none(float_or_none(opts
.audioquality
)) # int_or_none prevents inf, nan
244 if audioquality
is None or audioquality
< 0:
245 parser
.error('invalid audio quality specified')
246 if opts
.recodevideo
is not None:
247 opts
.recodevideo
= opts
.recodevideo
.replace(' ', '')
248 if not re
.match(FFmpegVideoConvertorPP
.FORMAT_RE
, opts
.recodevideo
):
249 parser
.error('invalid video remux format specified')
250 if opts
.remuxvideo
is not None:
251 opts
.remuxvideo
= opts
.remuxvideo
.replace(' ', '')
252 if not re
.match(FFmpegVideoRemuxerPP
.FORMAT_RE
, opts
.remuxvideo
):
253 parser
.error('invalid video remux format specified')
254 if opts
.convertsubtitles
is not None:
255 if opts
.convertsubtitles
not in FFmpegSubtitlesConvertorPP
.SUPPORTED_EXTS
:
256 parser
.error('invalid subtitle format specified')
257 if opts
.convertthumbnails
is not None:
258 if opts
.convertthumbnails
not in FFmpegThumbnailsConvertorPP
.SUPPORTED_EXTS
:
259 parser
.error('invalid thumbnail format specified')
261 if opts
.cookiesfrombrowser
is not None:
262 opts
.cookiesfrombrowser
= [
263 part
.strip() or None for part
in opts
.cookiesfrombrowser
.split(':', 1)]
264 if opts
.cookiesfrombrowser
[0].lower() not in SUPPORTED_BROWSERS
:
265 parser
.error('unsupported browser specified for cookies')
267 if opts
.date
is not None:
268 date
= DateRange
.day(opts
.date
)
270 date
= DateRange(opts
.dateafter
, opts
.datebefore
)
272 compat_opts
= opts
.compat_opts
274 def report_conflict(arg1
, arg2
):
275 warnings
.append(f
'{arg2} is ignored since {arg1} was given')
277 def _unused_compat_opt(name
):
278 if name
not in compat_opts
:
280 compat_opts
.discard(name
)
281 compat_opts
.update(['*%s' % name
])
284 def set_default_compat(compat_name
, opt_name
, default
=True, remove_compat
=True):
285 attr
= getattr(opts
, opt_name
)
286 if compat_name
in compat_opts
:
288 setattr(opts
, opt_name
, not default
)
292 _unused_compat_opt(compat_name
)
295 setattr(opts
, opt_name
, default
)
298 set_default_compat('abort-on-error', 'ignoreerrors', 'only_download')
299 set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
300 set_default_compat('no-clean-infojson', 'clean_infojson')
301 if 'no-attach-info-json' in compat_opts
:
302 if opts
.embed_infojson
:
303 _unused_compat_opt('no-attach-info-json')
305 opts
.embed_infojson
= False
306 if 'format-sort' in compat_opts
:
307 opts
.format_sort
.extend(InfoExtractor
.FormatSort
.ytdl_default
)
308 _video_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat
=False)
309 _audio_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat
=False)
310 if _video_multistreams_set
is False and _audio_multistreams_set
is False:
311 _unused_compat_opt('multistreams')
312 outtmpl_default
= opts
.outtmpl
.get('default')
314 if outtmpl_default
is None:
315 outtmpl_default
= opts
.outtmpl
['default'] = '%(id)s.%(ext)s'
317 report_conflict('--output', '--id')
318 if 'filename' in compat_opts
:
319 if outtmpl_default
is None:
320 outtmpl_default
= opts
.outtmpl
['default'] = '%(title)s-%(id)s.%(ext)s'
322 _unused_compat_opt('filename')
324 def validate_outtmpl(tmpl
, msg
):
325 err
= YoutubeDL
.validate_outtmpl(tmpl
)
327 parser
.error('invalid %s %r: %s' % (msg
, tmpl
, error_to_compat_str(err
)))
329 for k
, tmpl
in opts
.outtmpl
.items():
330 validate_outtmpl(tmpl
, f
'{k} output template')
331 opts
.forceprint
= opts
.forceprint
or []
332 for tmpl
in opts
.forceprint
or []:
333 validate_outtmpl(tmpl
, 'print template')
334 validate_outtmpl(opts
.sponsorblock_chapter_title
, 'SponsorBlock chapter title')
335 for k
, tmpl
in opts
.progress_template
.items():
336 k
= f
'{k[:-6]} console title' if '-title' in k
else f
'{k} progress'
337 validate_outtmpl(tmpl
, f
'{k} template')
339 if opts
.extractaudio
and not opts
.keepvideo
and opts
.format
is None:
340 opts
.format
= 'bestaudio/best'
342 if outtmpl_default
is not None and not os
.path
.splitext(outtmpl_default
)[1] and opts
.extractaudio
:
343 parser
.error('Cannot download a video and extract audio into the same'
344 ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
345 ' template'.format(outtmpl_default
))
347 for f
in opts
.format_sort
:
348 if re
.match(InfoExtractor
.FormatSort
.regex
, f
) is None:
349 parser
.error('invalid format sort string "%s" specified' % f
)
351 def metadataparser_actions(f
):
352 if isinstance(f
, str):
353 cmd
= '--parse-metadata %s' % compat_shlex_quote(f
)
355 actions
= [MetadataFromFieldPP
.to_action(f
)]
356 except Exception as err
:
357 parser
.error(f
'{cmd} is invalid; {err}')
359 cmd
= '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote
, f
))
360 actions
= ((MetadataParserPP
.Actions
.REPLACE
, x
, *f
[1:]) for x
in f
[0].split(','))
362 for action
in actions
:
364 MetadataParserPP
.validate_action(*action
)
365 except Exception as err
:
366 parser
.error(f
'{cmd} is invalid; {err}')
369 if opts
.parse_metadata
is None:
370 opts
.parse_metadata
= []
371 if opts
.metafromtitle
is not None:
372 opts
.parse_metadata
.append('title:%s' % opts
.metafromtitle
)
373 opts
.parse_metadata
= list(itertools
.chain(*map(metadataparser_actions
, opts
.parse_metadata
)))
375 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
376 any_printing
= opts
.print_json
377 download_archive_fn
= expand_path(opts
.download_archive
) if opts
.download_archive
is not None else opts
.download_archive
379 # If JSON is not printed anywhere, but comments are requested, save it to file
380 printing_json
= opts
.dumpjson
or opts
.print_json
or opts
.dump_single_json
381 if opts
.getcomments
and not printing_json
:
382 opts
.writeinfojson
= True
384 if opts
.no_sponsorblock
:
385 opts
.sponsorblock_mark
= set()
386 opts
.sponsorblock_remove
= set()
387 sponsorblock_query
= opts
.sponsorblock_mark | opts
.sponsorblock_remove
389 opts
.remove_chapters
= opts
.remove_chapters
or []
391 if (opts
.remove_chapters
or sponsorblock_query
) and opts
.sponskrub
is not False:
393 if opts
.remove_chapters
:
394 report_conflict('--remove-chapters', '--sponskrub')
395 if opts
.sponsorblock_mark
:
396 report_conflict('--sponsorblock-mark', '--sponskrub')
397 if opts
.sponsorblock_remove
:
398 report_conflict('--sponsorblock-remove', '--sponskrub')
399 opts
.sponskrub
= False
400 if opts
.sponskrub_cut
and opts
.split_chapters
and opts
.sponskrub
is not False:
401 report_conflict('--split-chapter', '--sponskrub-cut')
402 opts
.sponskrub_cut
= False
404 if opts
.remuxvideo
and opts
.recodevideo
:
405 report_conflict('--recode-video', '--remux-video')
406 opts
.remuxvideo
= False
408 if opts
.allow_unplayable_formats
:
409 def report_unplayable_conflict(opt_name
, arg
, default
=False, allowed
=None):
410 val
= getattr(opts
, opt_name
)
411 if (not allowed
and val
) or (allowed
and not allowed(val
)):
412 report_conflict('--allow-unplayable-formats', arg
)
413 setattr(opts
, opt_name
, default
)
415 report_unplayable_conflict('extractaudio', '--extract-audio')
416 report_unplayable_conflict('remuxvideo', '--remux-video')
417 report_unplayable_conflict('recodevideo', '--recode-video')
418 report_unplayable_conflict('addmetadata', '--embed-metadata')
419 report_unplayable_conflict('addchapters', '--embed-chapters')
420 report_unplayable_conflict('embed_infojson', '--embed-info-json')
421 opts
.embed_infojson
= False
422 report_unplayable_conflict('embedsubtitles', '--embed-subs')
423 report_unplayable_conflict('embedthumbnail', '--embed-thumbnail')
424 report_unplayable_conflict('xattrs', '--xattrs')
425 report_unplayable_conflict('fixup', '--fixup', default
='never', allowed
=lambda x
: x
in (None, 'never', 'ignore'))
427 report_unplayable_conflict('remove_chapters', '--remove-chapters', default
=[])
428 report_unplayable_conflict('sponsorblock_remove', '--sponsorblock-remove', default
=set())
429 report_unplayable_conflict('sponskrub', '--sponskrub', default
=set())
430 opts
.sponskrub
= False
432 if (opts
.addmetadata
or opts
.sponsorblock_mark
) and opts
.addchapters
is None:
433 opts
.addchapters
= True
436 postprocessors
= list(opts
.add_postprocessors
)
437 if sponsorblock_query
:
438 postprocessors
.append({
439 'key': 'SponsorBlock',
440 'categories': sponsorblock_query
,
441 'api': opts
.sponsorblock_api
,
442 # Run this immediately after extraction is complete
443 'when': 'pre_process'
445 if opts
.parse_metadata
:
446 postprocessors
.append({
447 'key': 'MetadataParser',
448 'actions': opts
.parse_metadata
,
449 # Run this immediately after extraction is complete
450 'when': 'pre_process'
452 if opts
.convertsubtitles
:
453 postprocessors
.append({
454 'key': 'FFmpegSubtitlesConvertor',
455 'format': opts
.convertsubtitles
,
456 # Run this before the actual video download
459 if opts
.convertthumbnails
:
460 postprocessors
.append({
461 'key': 'FFmpegThumbnailsConvertor',
462 'format': opts
.convertthumbnails
,
463 # Run this before the actual video download
466 # Must be after all other before_dl
467 if opts
.exec_before_dl_cmd
:
468 postprocessors
.append({
470 'exec_cmd': opts
.exec_before_dl_cmd
,
473 if opts
.extractaudio
:
474 postprocessors
.append({
475 'key': 'FFmpegExtractAudio',
476 'preferredcodec': opts
.audioformat
,
477 'preferredquality': opts
.audioquality
,
478 'nopostoverwrites': opts
.nopostoverwrites
,
481 postprocessors
.append({
482 'key': 'FFmpegVideoRemuxer',
483 'preferedformat': opts
.remuxvideo
,
486 postprocessors
.append({
487 'key': 'FFmpegVideoConvertor',
488 'preferedformat': opts
.recodevideo
,
490 # If ModifyChapters is going to remove chapters, subtitles must already be in the container.
491 if opts
.embedsubtitles
:
492 already_have_subtitle
= opts
.writesubtitles
and 'no-keep-subs' not in compat_opts
493 postprocessors
.append({
494 'key': 'FFmpegEmbedSubtitle',
495 # already_have_subtitle = True prevents the file from being deleted after embedding
496 'already_have_subtitle': already_have_subtitle
498 if not opts
.writeautomaticsub
and 'no-keep-subs' not in compat_opts
:
499 opts
.writesubtitles
= True
500 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
501 # this was the old behaviour if only --all-sub was given.
502 if opts
.allsubtitles
and not opts
.writeautomaticsub
:
503 opts
.writesubtitles
= True
504 # ModifyChapters must run before FFmpegMetadataPP
505 remove_chapters_patterns
, remove_ranges
= [], []
506 for regex
in opts
.remove_chapters
:
507 if regex
.startswith('*'):
508 dur
= list(map(parse_duration
, regex
[1:].split('-')))
509 if len(dur
) == 2 and all(t
is not None for t
in dur
):
510 remove_ranges
.append(tuple(dur
))
512 parser
.error(f
'invalid --remove-chapters time range {regex!r}. Must be of the form ?start-end')
514 remove_chapters_patterns
.append(re
.compile(regex
))
515 except re
.error
as err
:
516 parser
.error(f
'invalid --remove-chapters regex {regex!r} - {err}')
517 if opts
.remove_chapters
or sponsorblock_query
:
518 postprocessors
.append({
519 'key': 'ModifyChapters',
520 'remove_chapters_patterns': remove_chapters_patterns
,
521 'remove_sponsor_segments': opts
.sponsorblock_remove
,
522 'remove_ranges': remove_ranges
,
523 'sponsorblock_chapter_title': opts
.sponsorblock_chapter_title
,
524 'force_keyframes': opts
.force_keyframes_at_cuts
526 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
527 # FFmpegExtractAudioPP as containers before conversion may not support
528 # metadata (3gp, webm, etc.)
529 # By default ffmpeg preserves metadata applicable for both
530 # source and target containers. From this point the container won't change,
531 # so metadata can be added here.
532 if opts
.addmetadata
or opts
.addchapters
or opts
.embed_infojson
:
533 if opts
.embed_infojson
is None:
534 opts
.embed_infojson
= 'if_exists'
535 postprocessors
.append({
536 'key': 'FFmpegMetadata',
537 'add_chapters': opts
.addchapters
,
538 'add_metadata': opts
.addmetadata
,
539 'add_infojson': opts
.embed_infojson
,
542 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
543 # but must be below EmbedSubtitle and FFmpegMetadata
544 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
545 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
546 if opts
.sponskrub
is not False:
547 postprocessors
.append({
549 'path': opts
.sponskrub_path
,
550 'args': opts
.sponskrub_args
,
551 'cut': opts
.sponskrub_cut
,
552 'force': opts
.sponskrub_force
,
553 'ignoreerror': opts
.sponskrub
is None,
555 if opts
.embedthumbnail
:
556 already_have_thumbnail
= opts
.writethumbnail
or opts
.write_all_thumbnails
557 postprocessors
.append({
558 'key': 'EmbedThumbnail',
559 # already_have_thumbnail = True prevents the file from being deleted after embedding
560 'already_have_thumbnail': already_have_thumbnail
562 if not already_have_thumbnail
:
563 opts
.writethumbnail
= True
564 opts
.outtmpl
['pl_thumbnail'] = ''
565 if opts
.split_chapters
:
566 postprocessors
.append({
567 'key': 'FFmpegSplitChapters',
568 'force_keyframes': opts
.force_keyframes_at_cuts
,
570 # XAttrMetadataPP should be run after post-processors that may change file contents
572 postprocessors
.append({'key': 'XAttrMetadata'}
)
573 # Exec must be the last PP
575 postprocessors
.append({
577 'exec_cmd': opts
.exec_cmd
,
578 # Run this only after the files have been moved to their final locations
582 def report_args_compat(arg
, name
):
583 warnings
.append('%s given without specifying name. The arguments will be given to all %s' % (arg
, name
))
585 if 'default' in opts
.external_downloader_args
:
586 report_args_compat('--downloader-args', 'external downloaders')
588 if 'default-compat' in opts
.postprocessor_args
and 'default' not in opts
.postprocessor_args
:
589 report_args_compat('--post-processor-args', 'post-processors')
590 opts
.postprocessor_args
.setdefault('sponskrub', [])
591 opts
.postprocessor_args
['default'] = opts
.postprocessor_args
['default-compat']
594 opts
.recodevideo
if opts
.recodevideo
in FFmpegVideoConvertorPP
.SUPPORTED_EXTS
595 else opts
.remuxvideo
if opts
.remuxvideo
in FFmpegVideoRemuxerPP
.SUPPORTED_EXTS
596 else opts
.audioformat
if (opts
.extractaudio
and opts
.audioformat
!= 'best')
600 None if opts
.match_filter
is None
601 else match_filter_func(opts
.match_filter
))
604 'usenetrc': opts
.usenetrc
,
605 'netrc_location': opts
.netrc_location
,
606 'username': opts
.username
,
607 'password': opts
.password
,
608 'twofactor': opts
.twofactor
,
609 'videopassword': opts
.videopassword
,
610 'ap_mso': opts
.ap_mso
,
611 'ap_username': opts
.ap_username
,
612 'ap_password': opts
.ap_password
,
613 'quiet': (opts
.quiet
or any_getting
or any_printing
),
614 'no_warnings': opts
.no_warnings
,
615 'forceurl': opts
.geturl
,
616 'forcetitle': opts
.gettitle
,
617 'forceid': opts
.getid
,
618 'forcethumbnail': opts
.getthumbnail
,
619 'forcedescription': opts
.getdescription
,
620 'forceduration': opts
.getduration
,
621 'forcefilename': opts
.getfilename
,
622 'forceformat': opts
.getformat
,
623 'forceprint': opts
.forceprint
,
624 'forcejson': opts
.dumpjson
or opts
.print_json
,
625 'dump_single_json': opts
.dump_single_json
,
626 'force_write_download_archive': opts
.force_write_download_archive
,
627 'simulate': (any_getting
or None) if opts
.simulate
is None else opts
.simulate
,
628 'skip_download': opts
.skip_download
,
629 'format': opts
.format
,
630 'allow_unplayable_formats': opts
.allow_unplayable_formats
,
631 'ignore_no_formats_error': opts
.ignore_no_formats_error
,
632 'format_sort': opts
.format_sort
,
633 'format_sort_force': opts
.format_sort_force
,
634 'allow_multiple_video_streams': opts
.allow_multiple_video_streams
,
635 'allow_multiple_audio_streams': opts
.allow_multiple_audio_streams
,
636 'check_formats': opts
.check_formats
,
637 'listformats': opts
.listformats
,
638 'listformats_table': opts
.listformats_table
,
639 'outtmpl': opts
.outtmpl
,
640 'outtmpl_na_placeholder': opts
.outtmpl_na_placeholder
,
642 'autonumber_size': opts
.autonumber_size
,
643 'autonumber_start': opts
.autonumber_start
,
644 'restrictfilenames': opts
.restrictfilenames
,
645 'windowsfilenames': opts
.windowsfilenames
,
646 'ignoreerrors': opts
.ignoreerrors
,
647 'force_generic_extractor': opts
.force_generic_extractor
,
648 'ratelimit': opts
.ratelimit
,
649 'throttledratelimit': opts
.throttledratelimit
,
650 'overwrites': opts
.overwrites
,
651 'retries': opts
.retries
,
652 'fragment_retries': opts
.fragment_retries
,
653 'extractor_retries': opts
.extractor_retries
,
654 'skip_unavailable_fragments': opts
.skip_unavailable_fragments
,
655 'keep_fragments': opts
.keep_fragments
,
656 'concurrent_fragment_downloads': opts
.concurrent_fragment_downloads
,
657 'buffersize': opts
.buffersize
,
658 'noresizebuffer': opts
.noresizebuffer
,
659 'http_chunk_size': opts
.http_chunk_size
,
660 'continuedl': opts
.continue_dl
,
661 'noprogress': opts
.quiet
if opts
.noprogress
is None else opts
.noprogress
,
662 'progress_with_newline': opts
.progress_with_newline
,
663 'progress_template': opts
.progress_template
,
664 'playliststart': opts
.playliststart
,
665 'playlistend': opts
.playlistend
,
666 'playlistreverse': opts
.playlist_reverse
,
667 'playlistrandom': opts
.playlist_random
,
668 'noplaylist': opts
.noplaylist
,
669 'logtostderr': outtmpl_default
== '-',
670 'consoletitle': opts
.consoletitle
,
671 'nopart': opts
.nopart
,
672 'updatetime': opts
.updatetime
,
673 'writedescription': opts
.writedescription
,
674 'writeannotations': opts
.writeannotations
,
675 'writeinfojson': opts
.writeinfojson
,
676 'allow_playlist_files': opts
.allow_playlist_files
,
677 'clean_infojson': opts
.clean_infojson
,
678 'getcomments': opts
.getcomments
,
679 'writethumbnail': opts
.writethumbnail
,
680 'write_all_thumbnails': opts
.write_all_thumbnails
,
681 'writelink': opts
.writelink
,
682 'writeurllink': opts
.writeurllink
,
683 'writewebloclink': opts
.writewebloclink
,
684 'writedesktoplink': opts
.writedesktoplink
,
685 'writesubtitles': opts
.writesubtitles
,
686 'writeautomaticsub': opts
.writeautomaticsub
,
687 'allsubtitles': opts
.allsubtitles
,
688 'listsubtitles': opts
.listsubtitles
,
689 'subtitlesformat': opts
.subtitlesformat
,
690 'subtitleslangs': opts
.subtitleslangs
,
691 'matchtitle': decodeOption(opts
.matchtitle
),
692 'rejecttitle': decodeOption(opts
.rejecttitle
),
693 'max_downloads': opts
.max_downloads
,
694 'prefer_free_formats': opts
.prefer_free_formats
,
695 'trim_file_name': opts
.trim_file_name
,
696 'verbose': opts
.verbose
,
697 'dump_intermediate_pages': opts
.dump_intermediate_pages
,
698 'write_pages': opts
.write_pages
,
700 'keepvideo': opts
.keepvideo
,
701 'min_filesize': opts
.min_filesize
,
702 'max_filesize': opts
.max_filesize
,
703 'min_views': opts
.min_views
,
704 'max_views': opts
.max_views
,
706 'cachedir': opts
.cachedir
,
707 'youtube_print_sig_code': opts
.youtube_print_sig_code
,
708 'age_limit': opts
.age_limit
,
709 'download_archive': download_archive_fn
,
710 'break_on_existing': opts
.break_on_existing
,
711 'break_on_reject': opts
.break_on_reject
,
712 'break_per_url': opts
.break_per_url
,
713 'skip_playlist_after_errors': opts
.skip_playlist_after_errors
,
714 'cookiefile': opts
.cookiefile
,
715 'cookiesfrombrowser': opts
.cookiesfrombrowser
,
716 'nocheckcertificate': opts
.no_check_certificate
,
717 'prefer_insecure': opts
.prefer_insecure
,
719 'socket_timeout': opts
.socket_timeout
,
720 'bidi_workaround': opts
.bidi_workaround
,
721 'debug_printtraffic': opts
.debug_printtraffic
,
722 'prefer_ffmpeg': opts
.prefer_ffmpeg
,
723 'include_ads': opts
.include_ads
,
724 'default_search': opts
.default_search
,
725 'dynamic_mpd': opts
.dynamic_mpd
,
726 'extractor_args': opts
.extractor_args
,
727 'youtube_include_dash_manifest': opts
.youtube_include_dash_manifest
,
728 'youtube_include_hls_manifest': opts
.youtube_include_hls_manifest
,
729 'encoding': opts
.encoding
,
730 'extract_flat': opts
.extract_flat
,
731 'wait_for_video': opts
.wait_for_video
,
732 'mark_watched': opts
.mark_watched
,
733 'merge_output_format': opts
.merge_output_format
,
734 'final_ext': final_ext
,
735 'postprocessors': postprocessors
,
737 'source_address': opts
.source_address
,
738 'call_home': opts
.call_home
,
739 'sleep_interval_requests': opts
.sleep_interval_requests
,
740 'sleep_interval': opts
.sleep_interval
,
741 'max_sleep_interval': opts
.max_sleep_interval
,
742 'sleep_interval_subtitles': opts
.sleep_interval_subtitles
,
743 'external_downloader': opts
.external_downloader
,
744 'list_thumbnails': opts
.list_thumbnails
,
745 'playlist_items': opts
.playlist_items
,
746 'xattr_set_filesize': opts
.xattr_set_filesize
,
747 'match_filter': match_filter
,
748 'no_color': opts
.no_color
,
749 'ffmpeg_location': opts
.ffmpeg_location
,
750 'hls_prefer_native': opts
.hls_prefer_native
,
751 'hls_use_mpegts': opts
.hls_use_mpegts
,
752 'hls_split_discontinuity': opts
.hls_split_discontinuity
,
753 'external_downloader_args': opts
.external_downloader_args
,
754 'postprocessor_args': opts
.postprocessor_args
,
755 'cn_verification_proxy': opts
.cn_verification_proxy
,
756 'geo_verification_proxy': opts
.geo_verification_proxy
,
757 'geo_bypass': opts
.geo_bypass
,
758 'geo_bypass_country': opts
.geo_bypass_country
,
759 'geo_bypass_ip_block': opts
.geo_bypass_ip_block
,
760 '_warnings': warnings
,
761 'compat_opts': compat_opts
,
764 with YoutubeDL(ydl_opts
) as ydl
:
765 actual_use
= len(all_urls
) or opts
.load_info_filename
773 # If updater returns True, exit. Required for windows
776 sys
.exit('ERROR: The program must exit for the update to complete')
781 if opts
.update_self
or opts
.rm_cachedir
:
784 ydl
.warn_if_short_id(sys
.argv
[1:] if argv
is None else argv
)
786 'You must provide at least one URL.\n'
787 'Type yt-dlp --help to see a list of all options.')
790 if opts
.load_info_filename
is not None:
791 retcode
= ydl
.download_with_info_file(expand_path(opts
.load_info_filename
))
793 retcode
= ydl
.download(all_urls
)
794 except (MaxDownloadsReached
, ExistingVideoReached
, RejectedVideoReached
):
795 ydl
.to_screen('Aborting remaining downloads')
804 except DownloadError
:
806 except SameFileError
as e
:
807 sys
.exit(f
'ERROR: {e}')
808 except KeyboardInterrupt:
809 sys
.exit('\nERROR: Interrupted by user')
810 except BrokenPipeError
as e
:
811 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
812 devnull
= os
.open(os
.devnull
, os
.O_WRONLY
)
813 os
.dup2(devnull
, sys
.stdout
.fileno())
814 sys
.exit(f
'\nERROR: {e}')
817 __all__
= ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']