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