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 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 parser
.error('Playlist start must be positive')
235 if opts
.playlistend
not in (-1, None) and opts
.playlistend
< opts
.playliststart
:
236 raise parser
.error('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')
260 if opts
.cookiesfrombrowser
is not None:
261 opts
.cookiesfrombrowser
= [
262 part
.strip() or None for part
in opts
.cookiesfrombrowser
.split(':', 1)]
263 if opts
.cookiesfrombrowser
[0].lower() not in SUPPORTED_BROWSERS
:
264 parser
.error('unsupported browser specified for cookies')
265 geo_bypass_code
= opts
.geo_bypass_ip_block
or opts
.geo_bypass_country
266 if geo_bypass_code
is not None:
268 GeoUtils
.random_ipv4(geo_bypass_code
)
270 parser
.error('unsupported geo-bypass country or ip-block')
272 if opts
.date
is not None:
273 date
= DateRange
.day(opts
.date
)
275 date
= DateRange(opts
.dateafter
, opts
.datebefore
)
277 compat_opts
= opts
.compat_opts
279 def report_conflict(arg1
, arg2
):
280 warnings
.append(f
'{arg2} is ignored since {arg1} was given')
282 def _unused_compat_opt(name
):
283 if name
not in compat_opts
:
285 compat_opts
.discard(name
)
286 compat_opts
.update(['*%s' % name
])
289 def set_default_compat(compat_name
, opt_name
, default
=True, remove_compat
=True):
290 attr
= getattr(opts
, opt_name
)
291 if compat_name
in compat_opts
:
293 setattr(opts
, opt_name
, not default
)
297 _unused_compat_opt(compat_name
)
300 setattr(opts
, opt_name
, default
)
303 set_default_compat('abort-on-error', 'ignoreerrors', 'only_download')
304 set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
305 set_default_compat('no-clean-infojson', 'clean_infojson')
306 if 'no-attach-info-json' in compat_opts
:
307 if opts
.embed_infojson
:
308 _unused_compat_opt('no-attach-info-json')
310 opts
.embed_infojson
= False
311 if 'format-sort' in compat_opts
:
312 opts
.format_sort
.extend(InfoExtractor
.FormatSort
.ytdl_default
)
313 _video_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat
=False)
314 _audio_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat
=False)
315 if _video_multistreams_set
is False and _audio_multistreams_set
is False:
316 _unused_compat_opt('multistreams')
317 outtmpl_default
= opts
.outtmpl
.get('default')
319 if outtmpl_default
is None:
320 outtmpl_default
= opts
.outtmpl
['default'] = '%(id)s.%(ext)s'
322 report_conflict('--output', '--id')
323 if 'filename' in compat_opts
:
324 if outtmpl_default
is None:
325 outtmpl_default
= opts
.outtmpl
['default'] = '%(title)s-%(id)s.%(ext)s'
327 _unused_compat_opt('filename')
329 def validate_outtmpl(tmpl
, msg
):
330 err
= YoutubeDL
.validate_outtmpl(tmpl
)
332 parser
.error('invalid %s %r: %s' % (msg
, tmpl
, error_to_compat_str(err
)))
334 for k
, tmpl
in opts
.outtmpl
.items():
335 validate_outtmpl(tmpl
, f
'{k} output template')
336 opts
.forceprint
= opts
.forceprint
or []
337 for tmpl
in opts
.forceprint
or []:
338 validate_outtmpl(tmpl
, 'print template')
339 validate_outtmpl(opts
.sponsorblock_chapter_title
, 'SponsorBlock chapter title')
340 for k
, tmpl
in opts
.progress_template
.items():
341 k
= f
'{k[:-6]} console title' if '-title' in k
else f
'{k} progress'
342 validate_outtmpl(tmpl
, f
'{k} template')
344 if opts
.extractaudio
and not opts
.keepvideo
and opts
.format
is None:
345 opts
.format
= 'bestaudio/best'
347 if outtmpl_default
is not None and not os
.path
.splitext(outtmpl_default
)[1] and opts
.extractaudio
:
348 parser
.error('Cannot download a video and extract audio into the same'
349 ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
350 ' template'.format(outtmpl_default
))
352 for f
in opts
.format_sort
:
353 if re
.match(InfoExtractor
.FormatSort
.regex
, f
) is None:
354 parser
.error('invalid format sort string "%s" specified' % f
)
356 def metadataparser_actions(f
):
357 if isinstance(f
, str):
358 cmd
= '--parse-metadata %s' % compat_shlex_quote(f
)
360 actions
= [MetadataFromFieldPP
.to_action(f
)]
361 except Exception as err
:
362 parser
.error(f
'{cmd} is invalid; {err}')
364 cmd
= '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote
, f
))
365 actions
= ((MetadataParserPP
.Actions
.REPLACE
, x
, *f
[1:]) for x
in f
[0].split(','))
367 for action
in actions
:
369 MetadataParserPP
.validate_action(*action
)
370 except Exception as err
:
371 parser
.error(f
'{cmd} is invalid; {err}')
374 if opts
.parse_metadata
is None:
375 opts
.parse_metadata
= []
376 if opts
.metafromtitle
is not None:
377 opts
.parse_metadata
.append('title:%s' % opts
.metafromtitle
)
378 opts
.parse_metadata
= list(itertools
.chain(*map(metadataparser_actions
, opts
.parse_metadata
)))
380 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
381 any_printing
= opts
.print_json
382 download_archive_fn
= expand_path(opts
.download_archive
) if opts
.download_archive
is not None else opts
.download_archive
384 # If JSON is not printed anywhere, but comments are requested, save it to file
385 printing_json
= opts
.dumpjson
or opts
.print_json
or opts
.dump_single_json
386 if opts
.getcomments
and not printing_json
:
387 opts
.writeinfojson
= True
389 if opts
.no_sponsorblock
:
390 opts
.sponsorblock_mark
= set()
391 opts
.sponsorblock_remove
= set()
392 sponsorblock_query
= opts
.sponsorblock_mark | opts
.sponsorblock_remove
394 opts
.remove_chapters
= opts
.remove_chapters
or []
396 if (opts
.remove_chapters
or sponsorblock_query
) and opts
.sponskrub
is not False:
398 if opts
.remove_chapters
:
399 report_conflict('--remove-chapters', '--sponskrub')
400 if opts
.sponsorblock_mark
:
401 report_conflict('--sponsorblock-mark', '--sponskrub')
402 if opts
.sponsorblock_remove
:
403 report_conflict('--sponsorblock-remove', '--sponskrub')
404 opts
.sponskrub
= False
405 if opts
.sponskrub_cut
and opts
.split_chapters
and opts
.sponskrub
is not False:
406 report_conflict('--split-chapter', '--sponskrub-cut')
407 opts
.sponskrub_cut
= False
409 if opts
.remuxvideo
and opts
.recodevideo
:
410 report_conflict('--recode-video', '--remux-video')
411 opts
.remuxvideo
= False
413 if opts
.allow_unplayable_formats
:
414 def report_unplayable_conflict(opt_name
, arg
, default
=False, allowed
=None):
415 val
= getattr(opts
, opt_name
)
416 if (not allowed
and val
) or (allowed
and not allowed(val
)):
417 report_conflict('--allow-unplayable-formats', arg
)
418 setattr(opts
, opt_name
, default
)
420 report_unplayable_conflict('extractaudio', '--extract-audio')
421 report_unplayable_conflict('remuxvideo', '--remux-video')
422 report_unplayable_conflict('recodevideo', '--recode-video')
423 report_unplayable_conflict('addmetadata', '--embed-metadata')
424 report_unplayable_conflict('addchapters', '--embed-chapters')
425 report_unplayable_conflict('embed_infojson', '--embed-info-json')
426 opts
.embed_infojson
= False
427 report_unplayable_conflict('embedsubtitles', '--embed-subs')
428 report_unplayable_conflict('embedthumbnail', '--embed-thumbnail')
429 report_unplayable_conflict('xattrs', '--xattrs')
430 report_unplayable_conflict('fixup', '--fixup', default
='never', allowed
=lambda x
: x
in (None, 'never', 'ignore'))
432 report_unplayable_conflict('remove_chapters', '--remove-chapters', default
=[])
433 report_unplayable_conflict('sponsorblock_remove', '--sponsorblock-remove', default
=set())
434 report_unplayable_conflict('sponskrub', '--sponskrub', default
=set())
435 opts
.sponskrub
= False
437 if (opts
.addmetadata
or opts
.sponsorblock_mark
) and opts
.addchapters
is None:
438 opts
.addchapters
= True
441 postprocessors
= list(opts
.add_postprocessors
)
442 if sponsorblock_query
:
443 postprocessors
.append({
444 'key': 'SponsorBlock',
445 'categories': sponsorblock_query
,
446 'api': opts
.sponsorblock_api
,
447 # Run this immediately after extraction is complete
448 'when': 'pre_process'
450 if opts
.parse_metadata
:
451 postprocessors
.append({
452 'key': 'MetadataParser',
453 'actions': opts
.parse_metadata
,
454 # Run this immediately after extraction is complete
455 'when': 'pre_process'
457 if opts
.convertsubtitles
:
458 postprocessors
.append({
459 'key': 'FFmpegSubtitlesConvertor',
460 'format': opts
.convertsubtitles
,
461 # Run this before the actual video download
464 if opts
.convertthumbnails
:
465 postprocessors
.append({
466 'key': 'FFmpegThumbnailsConvertor',
467 'format': opts
.convertthumbnails
,
468 # Run this before the actual video download
471 # Must be after all other before_dl
472 if opts
.exec_before_dl_cmd
:
473 postprocessors
.append({
475 'exec_cmd': opts
.exec_before_dl_cmd
,
478 if opts
.extractaudio
:
479 postprocessors
.append({
480 'key': 'FFmpegExtractAudio',
481 'preferredcodec': opts
.audioformat
,
482 'preferredquality': opts
.audioquality
,
483 'nopostoverwrites': opts
.nopostoverwrites
,
486 postprocessors
.append({
487 'key': 'FFmpegVideoRemuxer',
488 'preferedformat': opts
.remuxvideo
,
491 postprocessors
.append({
492 'key': 'FFmpegVideoConvertor',
493 'preferedformat': opts
.recodevideo
,
495 # If ModifyChapters is going to remove chapters, subtitles must already be in the container.
496 if opts
.embedsubtitles
:
497 already_have_subtitle
= opts
.writesubtitles
and 'no-keep-subs' not in compat_opts
498 postprocessors
.append({
499 'key': 'FFmpegEmbedSubtitle',
500 # already_have_subtitle = True prevents the file from being deleted after embedding
501 'already_have_subtitle': already_have_subtitle
503 if not opts
.writeautomaticsub
and 'no-keep-subs' not in compat_opts
:
504 opts
.writesubtitles
= True
505 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
506 # this was the old behaviour if only --all-sub was given.
507 if opts
.allsubtitles
and not opts
.writeautomaticsub
:
508 opts
.writesubtitles
= True
509 # ModifyChapters must run before FFmpegMetadataPP
510 remove_chapters_patterns
, remove_ranges
= [], []
511 for regex
in opts
.remove_chapters
:
512 if regex
.startswith('*'):
513 dur
= list(map(parse_duration
, regex
[1:].split('-')))
514 if len(dur
) == 2 and all(t
is not None for t
in dur
):
515 remove_ranges
.append(tuple(dur
))
517 parser
.error(f
'invalid --remove-chapters time range {regex!r}. Must be of the form ?start-end')
519 remove_chapters_patterns
.append(re
.compile(regex
))
520 except re
.error
as err
:
521 parser
.error(f
'invalid --remove-chapters regex {regex!r} - {err}')
522 if opts
.remove_chapters
or sponsorblock_query
:
523 postprocessors
.append({
524 'key': 'ModifyChapters',
525 'remove_chapters_patterns': remove_chapters_patterns
,
526 'remove_sponsor_segments': opts
.sponsorblock_remove
,
527 'remove_ranges': remove_ranges
,
528 'sponsorblock_chapter_title': opts
.sponsorblock_chapter_title
,
529 'force_keyframes': opts
.force_keyframes_at_cuts
531 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
532 # FFmpegExtractAudioPP as containers before conversion may not support
533 # metadata (3gp, webm, etc.)
534 # By default ffmpeg preserves metadata applicable for both
535 # source and target containers. From this point the container won't change,
536 # so metadata can be added here.
537 if opts
.addmetadata
or opts
.addchapters
or opts
.embed_infojson
:
538 if opts
.embed_infojson
is None:
539 opts
.embed_infojson
= 'if_exists'
540 postprocessors
.append({
541 'key': 'FFmpegMetadata',
542 'add_chapters': opts
.addchapters
,
543 'add_metadata': opts
.addmetadata
,
544 'add_infojson': opts
.embed_infojson
,
547 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
548 # but must be below EmbedSubtitle and FFmpegMetadata
549 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
550 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
551 if opts
.sponskrub
is not False:
552 postprocessors
.append({
554 'path': opts
.sponskrub_path
,
555 'args': opts
.sponskrub_args
,
556 'cut': opts
.sponskrub_cut
,
557 'force': opts
.sponskrub_force
,
558 'ignoreerror': opts
.sponskrub
is None,
561 if opts
.embedthumbnail
:
562 already_have_thumbnail
= opts
.writethumbnail
or opts
.write_all_thumbnails
563 postprocessors
.append({
564 'key': 'EmbedThumbnail',
565 # already_have_thumbnail = True prevents the file from being deleted after embedding
566 'already_have_thumbnail': already_have_thumbnail
568 if not already_have_thumbnail
:
569 opts
.writethumbnail
= True
570 opts
.outtmpl
['pl_thumbnail'] = ''
571 if opts
.split_chapters
:
572 postprocessors
.append({
573 'key': 'FFmpegSplitChapters',
574 'force_keyframes': opts
.force_keyframes_at_cuts
,
576 # XAttrMetadataPP should be run after post-processors that may change file contents
578 postprocessors
.append({'key': 'XAttrMetadata'}
)
579 # Exec must be the last PP
581 postprocessors
.append({
583 'exec_cmd': opts
.exec_cmd
,
584 # Run this only after the files have been moved to their final locations
588 def report_args_compat(arg
, name
):
589 warnings
.append('%s given without specifying name. The arguments will be given to all %s' % (arg
, name
))
591 if 'default' in opts
.external_downloader_args
:
592 report_args_compat('--downloader-args', 'external downloaders')
594 if 'default-compat' in opts
.postprocessor_args
and 'default' not in opts
.postprocessor_args
:
595 report_args_compat('--post-processor-args', 'post-processors')
596 opts
.postprocessor_args
.setdefault('sponskrub', [])
597 opts
.postprocessor_args
['default'] = opts
.postprocessor_args
['default-compat']
599 def report_deprecation(val
, old
, new
=None):
602 deprecation_warnings
.append(
603 f
'{old} is deprecated and may be removed in a future version. Use {new} instead' if new
604 else f
'{old} is deprecated and may not work as expected')
606 report_deprecation(opts
.sponskrub
, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove')
607 report_deprecation(not opts
.prefer_ffmpeg
, '--prefer-avconv', 'ffmpeg')
608 report_deprecation(opts
.include_ads
, '--include-ads')
609 # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future
610 # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it
613 opts
.recodevideo
if opts
.recodevideo
in FFmpegVideoConvertorPP
.SUPPORTED_EXTS
614 else opts
.remuxvideo
if opts
.remuxvideo
in FFmpegVideoRemuxerPP
.SUPPORTED_EXTS
615 else opts
.audioformat
if (opts
.extractaudio
and opts
.audioformat
!= 'best')
619 None if opts
.match_filter
is None
620 else match_filter_func(opts
.match_filter
))
623 'usenetrc': opts
.usenetrc
,
624 'netrc_location': opts
.netrc_location
,
625 'username': opts
.username
,
626 'password': opts
.password
,
627 'twofactor': opts
.twofactor
,
628 'videopassword': opts
.videopassword
,
629 'ap_mso': opts
.ap_mso
,
630 'ap_username': opts
.ap_username
,
631 'ap_password': opts
.ap_password
,
632 'quiet': (opts
.quiet
or any_getting
or any_printing
),
633 'no_warnings': opts
.no_warnings
,
634 'forceurl': opts
.geturl
,
635 'forcetitle': opts
.gettitle
,
636 'forceid': opts
.getid
,
637 'forcethumbnail': opts
.getthumbnail
,
638 'forcedescription': opts
.getdescription
,
639 'forceduration': opts
.getduration
,
640 'forcefilename': opts
.getfilename
,
641 'forceformat': opts
.getformat
,
642 'forceprint': opts
.forceprint
,
643 'forcejson': opts
.dumpjson
or opts
.print_json
,
644 'dump_single_json': opts
.dump_single_json
,
645 'force_write_download_archive': opts
.force_write_download_archive
,
646 'simulate': (any_getting
or None) if opts
.simulate
is None else opts
.simulate
,
647 'skip_download': opts
.skip_download
,
648 'format': opts
.format
,
649 'allow_unplayable_formats': opts
.allow_unplayable_formats
,
650 'ignore_no_formats_error': opts
.ignore_no_formats_error
,
651 'format_sort': opts
.format_sort
,
652 'format_sort_force': opts
.format_sort_force
,
653 'allow_multiple_video_streams': opts
.allow_multiple_video_streams
,
654 'allow_multiple_audio_streams': opts
.allow_multiple_audio_streams
,
655 'check_formats': opts
.check_formats
,
656 'listformats': opts
.listformats
,
657 'listformats_table': opts
.listformats_table
,
658 'outtmpl': opts
.outtmpl
,
659 'outtmpl_na_placeholder': opts
.outtmpl_na_placeholder
,
661 'autonumber_size': opts
.autonumber_size
,
662 'autonumber_start': opts
.autonumber_start
,
663 'restrictfilenames': opts
.restrictfilenames
,
664 'windowsfilenames': opts
.windowsfilenames
,
665 'ignoreerrors': opts
.ignoreerrors
,
666 'force_generic_extractor': opts
.force_generic_extractor
,
667 'ratelimit': opts
.ratelimit
,
668 'throttledratelimit': opts
.throttledratelimit
,
669 'overwrites': opts
.overwrites
,
670 'retries': opts
.retries
,
671 'fragment_retries': opts
.fragment_retries
,
672 'extractor_retries': opts
.extractor_retries
,
673 'skip_unavailable_fragments': opts
.skip_unavailable_fragments
,
674 'keep_fragments': opts
.keep_fragments
,
675 'concurrent_fragment_downloads': opts
.concurrent_fragment_downloads
,
676 'buffersize': opts
.buffersize
,
677 'noresizebuffer': opts
.noresizebuffer
,
678 'http_chunk_size': opts
.http_chunk_size
,
679 'continuedl': opts
.continue_dl
,
680 'noprogress': opts
.quiet
if opts
.noprogress
is None else opts
.noprogress
,
681 'progress_with_newline': opts
.progress_with_newline
,
682 'progress_template': opts
.progress_template
,
683 'playliststart': opts
.playliststart
,
684 'playlistend': opts
.playlistend
,
685 'playlistreverse': opts
.playlist_reverse
,
686 'playlistrandom': opts
.playlist_random
,
687 'noplaylist': opts
.noplaylist
,
688 'logtostderr': outtmpl_default
== '-',
689 'consoletitle': opts
.consoletitle
,
690 'nopart': opts
.nopart
,
691 'updatetime': opts
.updatetime
,
692 'writedescription': opts
.writedescription
,
693 'writeannotations': opts
.writeannotations
,
694 'writeinfojson': opts
.writeinfojson
,
695 'allow_playlist_files': opts
.allow_playlist_files
,
696 'clean_infojson': opts
.clean_infojson
,
697 'getcomments': opts
.getcomments
,
698 'writethumbnail': opts
.writethumbnail
,
699 'write_all_thumbnails': opts
.write_all_thumbnails
,
700 'writelink': opts
.writelink
,
701 'writeurllink': opts
.writeurllink
,
702 'writewebloclink': opts
.writewebloclink
,
703 'writedesktoplink': opts
.writedesktoplink
,
704 'writesubtitles': opts
.writesubtitles
,
705 'writeautomaticsub': opts
.writeautomaticsub
,
706 'allsubtitles': opts
.allsubtitles
,
707 'listsubtitles': opts
.listsubtitles
,
708 'subtitlesformat': opts
.subtitlesformat
,
709 'subtitleslangs': opts
.subtitleslangs
,
710 'matchtitle': decodeOption(opts
.matchtitle
),
711 'rejecttitle': decodeOption(opts
.rejecttitle
),
712 'max_downloads': opts
.max_downloads
,
713 'prefer_free_formats': opts
.prefer_free_formats
,
714 'trim_file_name': opts
.trim_file_name
,
715 'verbose': opts
.verbose
,
716 'dump_intermediate_pages': opts
.dump_intermediate_pages
,
717 'write_pages': opts
.write_pages
,
719 'keepvideo': opts
.keepvideo
,
720 'min_filesize': opts
.min_filesize
,
721 'max_filesize': opts
.max_filesize
,
722 'min_views': opts
.min_views
,
723 'max_views': opts
.max_views
,
725 'cachedir': opts
.cachedir
,
726 'youtube_print_sig_code': opts
.youtube_print_sig_code
,
727 'age_limit': opts
.age_limit
,
728 'download_archive': download_archive_fn
,
729 'break_on_existing': opts
.break_on_existing
,
730 'break_on_reject': opts
.break_on_reject
,
731 'break_per_url': opts
.break_per_url
,
732 'skip_playlist_after_errors': opts
.skip_playlist_after_errors
,
733 'cookiefile': opts
.cookiefile
,
734 'cookiesfrombrowser': opts
.cookiesfrombrowser
,
735 'nocheckcertificate': opts
.no_check_certificate
,
736 'prefer_insecure': opts
.prefer_insecure
,
738 'socket_timeout': opts
.socket_timeout
,
739 'bidi_workaround': opts
.bidi_workaround
,
740 'debug_printtraffic': opts
.debug_printtraffic
,
741 'prefer_ffmpeg': opts
.prefer_ffmpeg
,
742 'include_ads': opts
.include_ads
,
743 'default_search': opts
.default_search
,
744 'dynamic_mpd': opts
.dynamic_mpd
,
745 'extractor_args': opts
.extractor_args
,
746 'youtube_include_dash_manifest': opts
.youtube_include_dash_manifest
,
747 'youtube_include_hls_manifest': opts
.youtube_include_hls_manifest
,
748 'encoding': opts
.encoding
,
749 'extract_flat': opts
.extract_flat
,
750 'wait_for_video': opts
.wait_for_video
,
751 'mark_watched': opts
.mark_watched
,
752 'merge_output_format': opts
.merge_output_format
,
753 'final_ext': final_ext
,
754 'postprocessors': postprocessors
,
756 'source_address': opts
.source_address
,
757 'call_home': opts
.call_home
,
758 'sleep_interval_requests': opts
.sleep_interval_requests
,
759 'sleep_interval': opts
.sleep_interval
,
760 'max_sleep_interval': opts
.max_sleep_interval
,
761 'sleep_interval_subtitles': opts
.sleep_interval_subtitles
,
762 'external_downloader': opts
.external_downloader
,
763 'list_thumbnails': opts
.list_thumbnails
,
764 'playlist_items': opts
.playlist_items
,
765 'xattr_set_filesize': opts
.xattr_set_filesize
,
766 'match_filter': match_filter
,
767 'no_color': opts
.no_color
,
768 'ffmpeg_location': opts
.ffmpeg_location
,
769 'hls_prefer_native': opts
.hls_prefer_native
,
770 'hls_use_mpegts': opts
.hls_use_mpegts
,
771 'hls_split_discontinuity': opts
.hls_split_discontinuity
,
772 'external_downloader_args': opts
.external_downloader_args
,
773 'postprocessor_args': opts
.postprocessor_args
,
774 'cn_verification_proxy': opts
.cn_verification_proxy
,
775 'geo_verification_proxy': opts
.geo_verification_proxy
,
776 'geo_bypass': opts
.geo_bypass
,
777 'geo_bypass_country': opts
.geo_bypass_country
,
778 'geo_bypass_ip_block': opts
.geo_bypass_ip_block
,
779 '_warnings': warnings
,
780 '_deprecation_warnings': deprecation_warnings
,
781 'compat_opts': compat_opts
,
784 with YoutubeDL(ydl_opts
) as ydl
:
785 actual_use
= all_urls
or opts
.load_info_filename
793 # If updater returns True, exit. Required for windows
796 sys
.exit('ERROR: The program must exit for the update to complete')
801 if opts
.update_self
or opts
.rm_cachedir
:
804 ydl
.warn_if_short_id(sys
.argv
[1:] if argv
is None else argv
)
806 'You must provide at least one URL.\n'
807 'Type yt-dlp --help to see a list of all options.')
810 if opts
.load_info_filename
is not None:
811 retcode
= ydl
.download_with_info_file(expand_path(opts
.load_info_filename
))
813 retcode
= ydl
.download(all_urls
)
814 except DownloadCancelled
:
815 ydl
.to_screen('Aborting remaining downloads')
824 except DownloadError
:
826 except SameFileError
as e
:
827 sys
.exit(f
'ERROR: {e}')
828 except KeyboardInterrupt:
829 sys
.exit('\nERROR: Interrupted by user')
830 except BrokenPipeError
as e
:
831 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
832 devnull
= os
.open(os
.devnull
, os
.O_WRONLY
)
833 os
.dup2(devnull
, sys
.stdout
.fileno())
834 sys
.exit(f
'\nERROR: {e}')
837 __all__
= ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']