4 f
'You are using an unsupported version of Python. Only Python versions 3.6 and above are supported by yt-dlp' # noqa: F541
6 __license__
= 'Public Domain'
16 from .options
import (
22 workaround_optparse_bug9161
,
24 from .cookies
import SUPPORTED_BROWSERS
45 from .update
import run_update
46 from .downloader
import (
49 from .extractor
import gen_extractors
, list_extractors
50 from .extractor
.common
import InfoExtractor
51 from .extractor
.adobepass
import MSO_INFO
52 from .postprocessor
import (
54 FFmpegSubtitlesConvertorPP
,
55 FFmpegThumbnailsConvertorPP
,
56 FFmpegVideoConvertorPP
,
61 from .YoutubeDL
import YoutubeDL
64 def _real_main(argv
=None):
65 # Compatibility fixes for Windows
66 if sys
.platform
== 'win32':
67 # https://github.com/ytdl-org/youtube-dl/issues/820
68 codecs
.register(lambda name
: codecs
.lookup('utf-8') if name
== 'cp65001' else None)
70 workaround_optparse_bug9161()
72 setproctitle('yt-dlp')
74 parser
, opts
, args
= parseOpts(argv
)
75 warnings
, deprecation_warnings
= [], []
78 if opts
.user_agent
is not None:
79 std_headers
['User-Agent'] = opts
.user_agent
82 if opts
.referer
is not None:
83 std_headers
['Referer'] = opts
.referer
86 std_headers
.update(opts
.headers
)
89 if opts
.dump_user_agent
:
90 write_string(std_headers
['User-Agent'] + '\n', out
=sys
.stdout
)
93 # Batch file verification
95 if opts
.batchfile
is not None:
97 if opts
.batchfile
== '-':
98 write_string('Reading URLs from stdin:\n')
102 expand_path(opts
.batchfile
),
103 'r', encoding
='utf-8', errors
='ignore')
104 batch_urls
= read_batch_urls(batchfd
)
106 write_string('[debug] Batch file urls: ' + repr(batch_urls
) + '\n')
108 sys
.exit('ERROR: batch file %s could not be read' % opts
.batchfile
)
109 all_urls
= batch_urls
+ [url
.strip() for url
in args
] # batch_urls are already striped in read_batch_urls
110 _enc
= preferredencoding()
111 all_urls
= [url
.decode(_enc
, 'ignore') if isinstance(url
, bytes) else url
for url
in all_urls
]
113 if opts
.list_extractors
:
114 for ie
in list_extractors(opts
.age_limit
):
115 write_string(ie
.IE_NAME
+ (' (CURRENTLY BROKEN)' if not ie
.working() else '') + '\n', out
=sys
.stdout
)
116 matchedUrls
= [url
for url
in all_urls
if ie
.suitable(url
)]
117 for mu
in matchedUrls
:
118 write_string(' ' + mu
+ '\n', out
=sys
.stdout
)
120 if opts
.list_extractor_descriptions
:
121 for ie
in list_extractors(opts
.age_limit
):
124 desc
= getattr(ie
, 'IE_DESC', ie
.IE_NAME
)
127 if getattr(ie
, 'SEARCH_KEY', None) is not None:
128 _SEARCHES
= ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
129 _COUNTS
= ('', '5', '10', 'all')
130 desc
+= f
'; "{ie.SEARCH_KEY}:" prefix (Example: "{ie.SEARCH_KEY}{random.choice(_COUNTS)}:{random.choice(_SEARCHES)}")'
131 write_string(desc
+ '\n', out
=sys
.stdout
)
134 table
= [[mso_id
, mso_info
['name']] for mso_id
, mso_info
in MSO_INFO
.items()]
135 write_string('Supported TV Providers:\n' + render_table(['mso', 'mso name'], table
) + '\n', out
=sys
.stdout
)
138 # Conflicting, missing and erroneous options
139 if opts
.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 parser
.error('Concurrent fragments must be positive')
199 if opts
.wait_for_video
is not None:
200 min_wait
, max_wait
, *_
= map(parse_duration
, opts
.wait_for_video
.split('-', 1) + [None])
201 if min_wait
is None or (max_wait
is None and '-' in opts
.wait_for_video
):
202 parser
.error('Invalid time range to wait')
203 elif max_wait
is not None and max_wait
< min_wait
:
204 parser
.error('Minimum time range to wait must not be longer than the maximum')
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 postprocessors
.append({
562 'key': 'EmbedThumbnail',
563 # already_have_thumbnail = True prevents the file from being deleted after embedding
564 'already_have_thumbnail': opts
.writethumbnail
566 if not opts
.writethumbnail
:
567 opts
.writethumbnail
= True
568 opts
.outtmpl
['pl_thumbnail'] = ''
569 if opts
.split_chapters
:
570 postprocessors
.append({
571 'key': 'FFmpegSplitChapters',
572 'force_keyframes': opts
.force_keyframes_at_cuts
,
574 # XAttrMetadataPP should be run after post-processors that may change file contents
576 postprocessors
.append({'key': 'XAttrMetadata'}
)
577 # Exec must be the last PP
579 postprocessors
.append({
581 'exec_cmd': opts
.exec_cmd
,
582 # Run this only after the files have been moved to their final locations
586 def report_args_compat(arg
, name
):
587 warnings
.append('%s given without specifying name. The arguments will be given to all %s' % (arg
, name
))
589 if 'default' in opts
.external_downloader_args
:
590 report_args_compat('--downloader-args', 'external downloaders')
592 if 'default-compat' in opts
.postprocessor_args
and 'default' not in opts
.postprocessor_args
:
593 report_args_compat('--post-processor-args', 'post-processors')
594 opts
.postprocessor_args
.setdefault('sponskrub', [])
595 opts
.postprocessor_args
['default'] = opts
.postprocessor_args
['default-compat']
597 def report_deprecation(val
, old
, new
=None):
600 deprecation_warnings
.append(
601 f
'{old} is deprecated and may be removed in a future version. Use {new} instead' if new
602 else f
'{old} is deprecated and may not work as expected')
604 report_deprecation(opts
.sponskrub
, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove')
605 report_deprecation(not opts
.prefer_ffmpeg
, '--prefer-avconv', 'ffmpeg')
606 report_deprecation(opts
.include_ads
, '--include-ads')
607 # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future
608 # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it
611 opts
.recodevideo
if opts
.recodevideo
in FFmpegVideoConvertorPP
.SUPPORTED_EXTS
612 else opts
.remuxvideo
if opts
.remuxvideo
in FFmpegVideoRemuxerPP
.SUPPORTED_EXTS
613 else opts
.audioformat
if (opts
.extractaudio
and opts
.audioformat
!= 'best')
617 None if opts
.match_filter
is None
618 else match_filter_func(opts
.match_filter
))
621 'usenetrc': opts
.usenetrc
,
622 'netrc_location': opts
.netrc_location
,
623 'username': opts
.username
,
624 'password': opts
.password
,
625 'twofactor': opts
.twofactor
,
626 'videopassword': opts
.videopassword
,
627 'ap_mso': opts
.ap_mso
,
628 'ap_username': opts
.ap_username
,
629 'ap_password': opts
.ap_password
,
630 'quiet': (opts
.quiet
or any_getting
or any_printing
),
631 'no_warnings': opts
.no_warnings
,
632 'forceurl': opts
.geturl
,
633 'forcetitle': opts
.gettitle
,
634 'forceid': opts
.getid
,
635 'forcethumbnail': opts
.getthumbnail
,
636 'forcedescription': opts
.getdescription
,
637 'forceduration': opts
.getduration
,
638 'forcefilename': opts
.getfilename
,
639 'forceformat': opts
.getformat
,
640 'forceprint': opts
.forceprint
,
641 'forcejson': opts
.dumpjson
or opts
.print_json
,
642 'dump_single_json': opts
.dump_single_json
,
643 'force_write_download_archive': opts
.force_write_download_archive
,
644 'simulate': (any_getting
or None) if opts
.simulate
is None else opts
.simulate
,
645 'skip_download': opts
.skip_download
,
646 'format': opts
.format
,
647 'allow_unplayable_formats': opts
.allow_unplayable_formats
,
648 'ignore_no_formats_error': opts
.ignore_no_formats_error
,
649 'format_sort': opts
.format_sort
,
650 'format_sort_force': opts
.format_sort_force
,
651 'allow_multiple_video_streams': opts
.allow_multiple_video_streams
,
652 'allow_multiple_audio_streams': opts
.allow_multiple_audio_streams
,
653 'check_formats': opts
.check_formats
,
654 'listformats': opts
.listformats
,
655 'listformats_table': opts
.listformats_table
,
656 'outtmpl': opts
.outtmpl
,
657 'outtmpl_na_placeholder': opts
.outtmpl_na_placeholder
,
659 'autonumber_size': opts
.autonumber_size
,
660 'autonumber_start': opts
.autonumber_start
,
661 'restrictfilenames': opts
.restrictfilenames
,
662 'windowsfilenames': opts
.windowsfilenames
,
663 'ignoreerrors': opts
.ignoreerrors
,
664 'force_generic_extractor': opts
.force_generic_extractor
,
665 'ratelimit': opts
.ratelimit
,
666 'throttledratelimit': opts
.throttledratelimit
,
667 'overwrites': opts
.overwrites
,
668 'retries': opts
.retries
,
669 'fragment_retries': opts
.fragment_retries
,
670 'extractor_retries': opts
.extractor_retries
,
671 'skip_unavailable_fragments': opts
.skip_unavailable_fragments
,
672 'keep_fragments': opts
.keep_fragments
,
673 'concurrent_fragment_downloads': opts
.concurrent_fragment_downloads
,
674 'buffersize': opts
.buffersize
,
675 'noresizebuffer': opts
.noresizebuffer
,
676 'http_chunk_size': opts
.http_chunk_size
,
677 'continuedl': opts
.continue_dl
,
678 'noprogress': opts
.quiet
if opts
.noprogress
is None else opts
.noprogress
,
679 'progress_with_newline': opts
.progress_with_newline
,
680 'progress_template': opts
.progress_template
,
681 'playliststart': opts
.playliststart
,
682 'playlistend': opts
.playlistend
,
683 'playlistreverse': opts
.playlist_reverse
,
684 'playlistrandom': opts
.playlist_random
,
685 'noplaylist': opts
.noplaylist
,
686 'logtostderr': outtmpl_default
== '-',
687 'consoletitle': opts
.consoletitle
,
688 'nopart': opts
.nopart
,
689 'updatetime': opts
.updatetime
,
690 'writedescription': opts
.writedescription
,
691 'writeannotations': opts
.writeannotations
,
692 'writeinfojson': opts
.writeinfojson
,
693 'allow_playlist_files': opts
.allow_playlist_files
,
694 'clean_infojson': opts
.clean_infojson
,
695 'getcomments': opts
.getcomments
,
696 'writethumbnail': opts
.writethumbnail
is True,
697 'write_all_thumbnails': opts
.writethumbnail
== 'all',
698 'writelink': opts
.writelink
,
699 'writeurllink': opts
.writeurllink
,
700 'writewebloclink': opts
.writewebloclink
,
701 'writedesktoplink': opts
.writedesktoplink
,
702 'writesubtitles': opts
.writesubtitles
,
703 'writeautomaticsub': opts
.writeautomaticsub
,
704 'allsubtitles': opts
.allsubtitles
,
705 'listsubtitles': opts
.listsubtitles
,
706 'subtitlesformat': opts
.subtitlesformat
,
707 'subtitleslangs': opts
.subtitleslangs
,
708 'matchtitle': decodeOption(opts
.matchtitle
),
709 'rejecttitle': decodeOption(opts
.rejecttitle
),
710 'max_downloads': opts
.max_downloads
,
711 'prefer_free_formats': opts
.prefer_free_formats
,
712 'trim_file_name': opts
.trim_file_name
,
713 'verbose': opts
.verbose
,
714 'dump_intermediate_pages': opts
.dump_intermediate_pages
,
715 'write_pages': opts
.write_pages
,
717 'keepvideo': opts
.keepvideo
,
718 'min_filesize': opts
.min_filesize
,
719 'max_filesize': opts
.max_filesize
,
720 'min_views': opts
.min_views
,
721 'max_views': opts
.max_views
,
723 'cachedir': opts
.cachedir
,
724 'youtube_print_sig_code': opts
.youtube_print_sig_code
,
725 'age_limit': opts
.age_limit
,
726 'download_archive': download_archive_fn
,
727 'break_on_existing': opts
.break_on_existing
,
728 'break_on_reject': opts
.break_on_reject
,
729 'break_per_url': opts
.break_per_url
,
730 'skip_playlist_after_errors': opts
.skip_playlist_after_errors
,
731 'cookiefile': opts
.cookiefile
,
732 'cookiesfrombrowser': opts
.cookiesfrombrowser
,
733 'nocheckcertificate': opts
.no_check_certificate
,
734 'prefer_insecure': opts
.prefer_insecure
,
736 'socket_timeout': opts
.socket_timeout
,
737 'bidi_workaround': opts
.bidi_workaround
,
738 'debug_printtraffic': opts
.debug_printtraffic
,
739 'prefer_ffmpeg': opts
.prefer_ffmpeg
,
740 'include_ads': opts
.include_ads
,
741 'default_search': opts
.default_search
,
742 'dynamic_mpd': opts
.dynamic_mpd
,
743 'extractor_args': opts
.extractor_args
,
744 'youtube_include_dash_manifest': opts
.youtube_include_dash_manifest
,
745 'youtube_include_hls_manifest': opts
.youtube_include_hls_manifest
,
746 'encoding': opts
.encoding
,
747 'extract_flat': opts
.extract_flat
,
748 'wait_for_video': opts
.wait_for_video
,
749 'mark_watched': opts
.mark_watched
,
750 'merge_output_format': opts
.merge_output_format
,
751 'final_ext': final_ext
,
752 'postprocessors': postprocessors
,
754 'source_address': opts
.source_address
,
755 'call_home': opts
.call_home
,
756 'sleep_interval_requests': opts
.sleep_interval_requests
,
757 'sleep_interval': opts
.sleep_interval
,
758 'max_sleep_interval': opts
.max_sleep_interval
,
759 'sleep_interval_subtitles': opts
.sleep_interval_subtitles
,
760 'external_downloader': opts
.external_downloader
,
761 'list_thumbnails': opts
.list_thumbnails
,
762 'playlist_items': opts
.playlist_items
,
763 'xattr_set_filesize': opts
.xattr_set_filesize
,
764 'match_filter': match_filter
,
765 'no_color': opts
.no_color
,
766 'ffmpeg_location': opts
.ffmpeg_location
,
767 'hls_prefer_native': opts
.hls_prefer_native
,
768 'hls_use_mpegts': opts
.hls_use_mpegts
,
769 'hls_split_discontinuity': opts
.hls_split_discontinuity
,
770 'external_downloader_args': opts
.external_downloader_args
,
771 'postprocessor_args': opts
.postprocessor_args
,
772 'cn_verification_proxy': opts
.cn_verification_proxy
,
773 'geo_verification_proxy': opts
.geo_verification_proxy
,
774 'geo_bypass': opts
.geo_bypass
,
775 'geo_bypass_country': opts
.geo_bypass_country
,
776 'geo_bypass_ip_block': opts
.geo_bypass_ip_block
,
777 '_warnings': warnings
,
778 '_deprecation_warnings': deprecation_warnings
,
779 'compat_opts': compat_opts
,
782 with YoutubeDL(ydl_opts
) as ydl
:
783 actual_use
= all_urls
or opts
.load_info_filename
791 # If updater returns True, exit. Required for windows
794 sys
.exit('ERROR: The program must exit for the update to complete')
799 if opts
.update_self
or opts
.rm_cachedir
:
802 ydl
.warn_if_short_id(sys
.argv
[1:] if argv
is None else argv
)
804 'You must provide at least one URL.\n'
805 'Type yt-dlp --help to see a list of all options.')
808 if opts
.load_info_filename
is not None:
809 retcode
= ydl
.download_with_info_file(expand_path(opts
.load_info_filename
))
811 retcode
= ydl
.download(all_urls
)
812 except DownloadCancelled
:
813 ydl
.to_screen('Aborting remaining downloads')
822 except DownloadError
:
824 except SameFileError
as e
:
825 sys
.exit(f
'ERROR: {e}')
826 except KeyboardInterrupt:
827 sys
.exit('\nERROR: Interrupted by user')
828 except BrokenPipeError
as e
:
829 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
830 devnull
= os
.open(os
.devnull
, os
.O_WRONLY
)
831 os
.dup2(devnull
, sys
.stdout
.fileno())
832 sys
.exit(f
'\nERROR: {e}')
835 __all__
= ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']