4 from __future__
import unicode_literals
6 __license__
= 'Public Domain'
16 from .options
import (
21 workaround_optparse_bug9161
,
23 from .cookies
import SUPPORTED_BROWSERS
42 from .update
import run_update
43 from .downloader
import (
46 from .extractor
import gen_extractors
, list_extractors
47 from .extractor
.common
import InfoExtractor
48 from .extractor
.adobepass
import MSO_INFO
49 from .postprocessor
.ffmpeg
import (
51 FFmpegSubtitlesConvertorPP
,
52 FFmpegThumbnailsConvertorPP
,
53 FFmpegVideoConvertorPP
,
56 from .postprocessor
.metadatafromfield
import MetadataFromFieldPP
57 from .YoutubeDL
import YoutubeDL
60 def _real_main(argv
=None):
61 # Compatibility fixes for Windows
62 if sys
.platform
== 'win32':
63 # https://github.com/ytdl-org/youtube-dl/issues/820
64 codecs
.register(lambda name
: codecs
.lookup('utf-8') if name
== 'cp65001' else None)
66 workaround_optparse_bug9161()
68 setproctitle('yt-dlp')
70 parser
, opts
, args
= parseOpts(argv
)
74 if opts
.user_agent
is not None:
75 std_headers
['User-Agent'] = opts
.user_agent
78 if opts
.referer
is not None:
79 std_headers
['Referer'] = opts
.referer
82 std_headers
.update(opts
.headers
)
85 if opts
.dump_user_agent
:
86 write_string(std_headers
['User-Agent'] + '\n', out
=sys
.stdout
)
89 # Batch file verification
91 if opts
.batchfile
is not None:
93 if opts
.batchfile
== '-':
97 expand_path(opts
.batchfile
),
98 'r', encoding
='utf-8', errors
='ignore')
99 batch_urls
= read_batch_urls(batchfd
)
101 write_string('[debug] Batch file urls: ' + repr(batch_urls
) + '\n')
103 sys
.exit('ERROR: batch file %s could not be read' % opts
.batchfile
)
104 all_urls
= batch_urls
+ [url
.strip() for url
in args
] # batch_urls are already striped in read_batch_urls
105 _enc
= preferredencoding()
106 all_urls
= [url
.decode(_enc
, 'ignore') if isinstance(url
, bytes) else url
for url
in all_urls
]
108 if opts
.list_extractors
:
109 for ie
in list_extractors(opts
.age_limit
):
110 write_string(ie
.IE_NAME
+ (' (CURRENTLY BROKEN)' if not ie
._WORKING
else '') + '\n', out
=sys
.stdout
)
111 matchedUrls
= [url
for url
in all_urls
if ie
.suitable(url
)]
112 for mu
in matchedUrls
:
113 write_string(' ' + mu
+ '\n', out
=sys
.stdout
)
115 if opts
.list_extractor_descriptions
:
116 for ie
in list_extractors(opts
.age_limit
):
119 desc
= getattr(ie
, 'IE_DESC', ie
.IE_NAME
)
122 if hasattr(ie
, 'SEARCH_KEY'):
123 _SEARCHES
= ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
124 _COUNTS
= ('', '5', '10', 'all')
125 desc
+= ' (Example: "%s%s:%s" )' % (ie
.SEARCH_KEY
, random
.choice(_COUNTS
), random
.choice(_SEARCHES
))
126 write_string(desc
+ '\n', out
=sys
.stdout
)
129 table
= [[mso_id
, mso_info
['name']] for mso_id
, mso_info
in MSO_INFO
.items()]
130 write_string('Supported TV Providers:\n' + render_table(['mso', 'mso name'], table
) + '\n', out
=sys
.stdout
)
133 # Conflicting, missing and erroneous options
134 if opts
.usenetrc
and (opts
.username
is not None or opts
.password
is not None):
135 parser
.error('using .netrc conflicts with giving username/password')
136 if opts
.password
is not None and opts
.username
is None:
137 parser
.error('account username missing\n')
138 if opts
.ap_password
is not None and opts
.ap_username
is None:
139 parser
.error('TV Provider account username missing\n')
140 if opts
.autonumber_size
is not None:
141 if opts
.autonumber_size
<= 0:
142 parser
.error('auto number size must be positive')
143 if opts
.autonumber_start
is not None:
144 if opts
.autonumber_start
< 0:
145 parser
.error('auto number start must be positive or 0')
146 if opts
.username
is not None and opts
.password
is None:
147 opts
.password
= compat_getpass('Type account password and press [Return]: ')
148 if opts
.ap_username
is not None and opts
.ap_password
is None:
149 opts
.ap_password
= compat_getpass('Type TV provider account password and press [Return]: ')
150 if opts
.ratelimit
is not None:
151 numeric_limit
= FileDownloader
.parse_bytes(opts
.ratelimit
)
152 if numeric_limit
is None:
153 parser
.error('invalid rate limit specified')
154 opts
.ratelimit
= numeric_limit
155 if opts
.throttledratelimit
is not None:
156 numeric_limit
= FileDownloader
.parse_bytes(opts
.throttledratelimit
)
157 if numeric_limit
is None:
158 parser
.error('invalid rate limit specified')
159 opts
.throttledratelimit
= numeric_limit
160 if opts
.min_filesize
is not None:
161 numeric_limit
= FileDownloader
.parse_bytes(opts
.min_filesize
)
162 if numeric_limit
is None:
163 parser
.error('invalid min_filesize specified')
164 opts
.min_filesize
= numeric_limit
165 if opts
.max_filesize
is not None:
166 numeric_limit
= FileDownloader
.parse_bytes(opts
.max_filesize
)
167 if numeric_limit
is None:
168 parser
.error('invalid max_filesize specified')
169 opts
.max_filesize
= numeric_limit
170 if opts
.sleep_interval
is not None:
171 if opts
.sleep_interval
< 0:
172 parser
.error('sleep interval must be positive or 0')
173 if opts
.max_sleep_interval
is not None:
174 if opts
.max_sleep_interval
< 0:
175 parser
.error('max sleep interval must be positive or 0')
176 if opts
.sleep_interval
is None:
177 parser
.error('min sleep interval must be specified, use --min-sleep-interval')
178 if opts
.max_sleep_interval
< opts
.sleep_interval
:
179 parser
.error('max sleep interval must be greater than or equal to min sleep interval')
181 opts
.max_sleep_interval
= opts
.sleep_interval
182 if opts
.sleep_interval_subtitles
is not None:
183 if opts
.sleep_interval_subtitles
< 0:
184 parser
.error('subtitles sleep interval must be positive or 0')
185 if opts
.sleep_interval_requests
is not None:
186 if opts
.sleep_interval_requests
< 0:
187 parser
.error('requests sleep interval must be positive or 0')
188 if opts
.ap_mso
and opts
.ap_mso
not in MSO_INFO
:
189 parser
.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
190 if opts
.overwrites
: # --yes-overwrites implies --no-continue
191 opts
.continue_dl
= False
192 if opts
.concurrent_fragment_downloads
<= 0:
193 raise ValueError('Concurrent fragments must be positive')
195 def parse_retries(retries
, name
=''):
196 if retries
in ('inf', 'infinite'):
197 parsed_retries
= float('inf')
200 parsed_retries
= int(retries
)
201 except (TypeError, ValueError):
202 parser
.error('invalid %sretry count specified' % name
)
203 return parsed_retries
204 if opts
.retries
is not None:
205 opts
.retries
= parse_retries(opts
.retries
)
206 if opts
.fragment_retries
is not None:
207 opts
.fragment_retries
= parse_retries(opts
.fragment_retries
, 'fragment ')
208 if opts
.extractor_retries
is not None:
209 opts
.extractor_retries
= parse_retries(opts
.extractor_retries
, 'extractor ')
210 if opts
.buffersize
is not None:
211 numeric_buffersize
= FileDownloader
.parse_bytes(opts
.buffersize
)
212 if numeric_buffersize
is None:
213 parser
.error('invalid buffer size specified')
214 opts
.buffersize
= numeric_buffersize
215 if opts
.http_chunk_size
is not None:
216 numeric_chunksize
= FileDownloader
.parse_bytes(opts
.http_chunk_size
)
217 if not numeric_chunksize
:
218 parser
.error('invalid http chunk size specified')
219 opts
.http_chunk_size
= numeric_chunksize
220 if opts
.playliststart
<= 0:
221 raise ValueError('Playlist start must be positive')
222 if opts
.playlistend
not in (-1, None) and opts
.playlistend
< opts
.playliststart
:
223 raise ValueError('Playlist end must be greater than playlist start')
224 if opts
.extractaudio
:
225 if opts
.audioformat
not in ['best'] + list(FFmpegExtractAudioPP
.SUPPORTED_EXTS
):
226 parser
.error('invalid audio format specified')
227 if opts
.audioquality
:
228 opts
.audioquality
= opts
.audioquality
.strip('k').strip('K')
229 if not opts
.audioquality
.isdigit():
230 parser
.error('invalid audio quality specified')
231 if opts
.recodevideo
is not None:
232 opts
.recodevideo
= opts
.recodevideo
.replace(' ', '')
233 if not re
.match(FFmpegVideoConvertorPP
.FORMAT_RE
, opts
.recodevideo
):
234 parser
.error('invalid video remux format specified')
235 if opts
.remuxvideo
is not None:
236 opts
.remuxvideo
= opts
.remuxvideo
.replace(' ', '')
237 if not re
.match(FFmpegVideoRemuxerPP
.FORMAT_RE
, opts
.remuxvideo
):
238 parser
.error('invalid video remux format specified')
239 if opts
.convertsubtitles
is not None:
240 if opts
.convertsubtitles
not in FFmpegSubtitlesConvertorPP
.SUPPORTED_EXTS
:
241 parser
.error('invalid subtitle format specified')
242 if opts
.convertthumbnails
is not None:
243 if opts
.convertthumbnails
not in FFmpegThumbnailsConvertorPP
.SUPPORTED_EXTS
:
244 parser
.error('invalid thumbnail format specified')
246 if opts
.cookiesfrombrowser
is not None:
247 opts
.cookiesfrombrowser
= [
248 part
.strip() or None for part
in opts
.cookiesfrombrowser
.split(':', 1)]
249 if opts
.cookiesfrombrowser
[0] not in SUPPORTED_BROWSERS
:
250 parser
.error('unsupported browser specified for cookies')
252 if opts
.date
is not None:
253 date
= DateRange
.day(opts
.date
)
255 date
= DateRange(opts
.dateafter
, opts
.datebefore
)
257 def parse_compat_opts():
258 parsed_compat_opts
, compat_opts
= set(), opts
.compat_opts
[::-1]
260 actual_opt
= opt
= compat_opts
.pop().lower()
261 if opt
== 'youtube-dl':
262 compat_opts
.extend(['-multistreams', 'all'])
263 elif opt
== 'youtube-dlc':
264 compat_opts
.extend(['-no-youtube-channel-redirect', '-no-live-chat', 'all'])
266 parsed_compat_opts
.update(all_compat_opts
)
268 parsed_compat_opts
= set()
272 parsed_compat_opts
.discard(opt
)
274 parsed_compat_opts
.update([opt
])
275 if opt
not in all_compat_opts
:
276 parser
.error('Invalid compatibility option %s' % actual_opt
)
277 return parsed_compat_opts
280 'filename', 'format-sort', 'abort-on-error', 'format-spec', 'no-playlist-metafiles',
281 'multistreams', 'no-live-chat', 'playlist-index', 'list-formats', 'no-direct-merge',
282 'no-youtube-channel-redirect', 'no-youtube-unavailable-videos', 'no-attach-info-json',
283 'embed-thumbnail-atomicparsley', 'seperate-video-versions', 'no-clean-infojson',
285 compat_opts
= parse_compat_opts()
287 def _unused_compat_opt(name
):
288 if name
not in compat_opts
:
290 compat_opts
.discard(name
)
291 compat_opts
.update(['*%s' % name
])
294 def set_default_compat(compat_name
, opt_name
, default
=True, remove_compat
=True):
295 attr
= getattr(opts
, opt_name
)
296 if compat_name
in compat_opts
:
298 setattr(opts
, opt_name
, not default
)
302 _unused_compat_opt(compat_name
)
305 setattr(opts
, opt_name
, default
)
308 set_default_compat('abort-on-error', 'ignoreerrors')
309 set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
310 set_default_compat('no-clean-infojson', 'clean_infojson')
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')
318 if 'filename' in compat_opts
:
319 if outtmpl_default
is None:
320 outtmpl_default
= '%(title)s-%(id)s.%(ext)s'
321 opts
.outtmpl
.update({'default': outtmpl_default}
)
323 _unused_compat_opt('filename')
325 def validate_outtmpl(tmpl
, msg
):
326 err
= YoutubeDL
.validate_outtmpl(tmpl
)
328 parser
.error('invalid %s %r: %s' % (msg
, tmpl
, error_to_compat_str(err
)))
330 for k
, tmpl
in opts
.outtmpl
.items():
331 validate_outtmpl(tmpl
, '%s output template' % k
)
332 for tmpl
in opts
.forceprint
:
333 validate_outtmpl(tmpl
, 'print template')
335 if opts
.extractaudio
and not opts
.keepvideo
and opts
.format
is None:
336 opts
.format
= 'bestaudio/best'
338 if outtmpl_default
is not None and not os
.path
.splitext(outtmpl_default
)[1] and opts
.extractaudio
:
339 parser
.error('Cannot download a video and extract audio into the same'
340 ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
341 ' template'.format(outtmpl_default
))
343 for f
in opts
.format_sort
:
344 if re
.match(InfoExtractor
.FormatSort
.regex
, f
) is None:
345 parser
.error('invalid format sort string "%s" specified' % f
)
347 if opts
.metafromfield
is None:
348 opts
.metafromfield
= []
349 if opts
.metafromtitle
is not None:
350 opts
.metafromfield
.append('title:%s' % opts
.metafromtitle
)
351 for f
in opts
.metafromfield
:
352 if re
.match(MetadataFromFieldPP
.regex
, f
) is None:
353 parser
.error('invalid format string "%s" specified for --parse-metadata' % f
)
355 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
356 any_printing
= opts
.print_json
357 download_archive_fn
= expand_path(opts
.download_archive
) if opts
.download_archive
is not None else opts
.download_archive
359 # If JSON is not printed anywhere, but comments are requested, save it to file
360 printing_json
= opts
.dumpjson
or opts
.print_json
or opts
.dump_single_json
361 if opts
.getcomments
and not printing_json
:
362 opts
.writeinfojson
= True
364 def report_conflict(arg1
, arg2
):
365 warnings
.append('%s is ignored since %s was given' % (arg2
, arg1
))
367 if opts
.remuxvideo
and opts
.recodevideo
:
368 report_conflict('--recode-video', '--remux-video')
369 opts
.remuxvideo
= False
370 if opts
.sponskrub_cut
and opts
.split_chapters
and opts
.sponskrub
is not False:
371 report_conflict('--split-chapter', '--sponskrub-cut')
372 opts
.sponskrub_cut
= False
374 if opts
.allow_unplayable_formats
:
375 if opts
.extractaudio
:
376 report_conflict('--allow-unplayable-formats', '--extract-audio')
377 opts
.extractaudio
= False
379 report_conflict('--allow-unplayable-formats', '--remux-video')
380 opts
.remuxvideo
= False
382 report_conflict('--allow-unplayable-formats', '--recode-video')
383 opts
.recodevideo
= False
385 report_conflict('--allow-unplayable-formats', '--add-metadata')
386 opts
.addmetadata
= False
387 if opts
.embedsubtitles
:
388 report_conflict('--allow-unplayable-formats', '--embed-subs')
389 opts
.embedsubtitles
= False
390 if opts
.embedthumbnail
:
391 report_conflict('--allow-unplayable-formats', '--embed-thumbnail')
392 opts
.embedthumbnail
= False
394 report_conflict('--allow-unplayable-formats', '--xattrs')
396 if opts
.fixup
and opts
.fixup
.lower() not in ('never', 'ignore'):
397 report_conflict('--allow-unplayable-formats', '--fixup')
400 report_conflict('--allow-unplayable-formats', '--sponskrub')
401 opts
.sponskrub
= False
405 if opts
.metafromfield
:
406 postprocessors
.append({
407 'key': 'MetadataFromField',
408 'formats': opts
.metafromfield
,
409 # Run this immediately after extraction is complete
410 'when': 'pre_process'
412 if opts
.convertsubtitles
:
413 postprocessors
.append({
414 'key': 'FFmpegSubtitlesConvertor',
415 'format': opts
.convertsubtitles
,
416 # Run this before the actual video download
419 if opts
.convertthumbnails
:
420 postprocessors
.append({
421 'key': 'FFmpegThumbnailsConvertor',
422 'format': opts
.convertthumbnails
,
423 # Run this before the actual video download
426 # Must be after all other before_dl
427 if opts
.exec_before_dl_cmd
:
428 postprocessors
.append({
429 'key': 'ExecAfterDownload',
430 'exec_cmd': opts
.exec_before_dl_cmd
,
433 if opts
.extractaudio
:
434 postprocessors
.append({
435 'key': 'FFmpegExtractAudio',
436 'preferredcodec': opts
.audioformat
,
437 'preferredquality': opts
.audioquality
,
438 'nopostoverwrites': opts
.nopostoverwrites
,
441 postprocessors
.append({
442 'key': 'FFmpegVideoRemuxer',
443 'preferedformat': opts
.remuxvideo
,
446 postprocessors
.append({
447 'key': 'FFmpegVideoConvertor',
448 'preferedformat': opts
.recodevideo
,
450 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
451 # FFmpegExtractAudioPP as containers before conversion may not support
452 # metadata (3gp, webm, etc.)
453 # And this post-processor should be placed before other metadata
454 # manipulating post-processors (FFmpegEmbedSubtitle) to prevent loss of
455 # extra metadata. By default ffmpeg preserves metadata applicable for both
456 # source and target containers. From this point the container won't change,
457 # so metadata can be added here.
459 postprocessors
.append({'key': 'FFmpegMetadata'}
)
460 if opts
.embedsubtitles
:
461 already_have_subtitle
= opts
.writesubtitles
462 postprocessors
.append({
463 'key': 'FFmpegEmbedSubtitle',
464 # already_have_subtitle = True prevents the file from being deleted after embedding
465 'already_have_subtitle': already_have_subtitle
467 if not already_have_subtitle
:
468 opts
.writesubtitles
= True
469 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
470 # this was the old behaviour if only --all-sub was given.
471 if opts
.allsubtitles
and not opts
.writeautomaticsub
:
472 opts
.writesubtitles
= True
473 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
474 # but must be below EmbedSubtitle and FFmpegMetadata
475 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
476 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
477 if opts
.sponskrub
is not False:
478 postprocessors
.append({
480 'path': opts
.sponskrub_path
,
481 'args': opts
.sponskrub_args
,
482 'cut': opts
.sponskrub_cut
,
483 'force': opts
.sponskrub_force
,
484 'ignoreerror': opts
.sponskrub
is None,
486 if opts
.embedthumbnail
:
487 already_have_thumbnail
= opts
.writethumbnail
or opts
.write_all_thumbnails
488 postprocessors
.append({
489 'key': 'EmbedThumbnail',
490 # already_have_thumbnail = True prevents the file from being deleted after embedding
491 'already_have_thumbnail': already_have_thumbnail
493 if not already_have_thumbnail
:
494 opts
.writethumbnail
= True
495 if opts
.split_chapters
:
496 postprocessors
.append({'key': 'FFmpegSplitChapters'}
)
497 # XAttrMetadataPP should be run after post-processors that may change file contents
499 postprocessors
.append({'key': 'XAttrMetadata'}
)
500 # ExecAfterDownload must be the last PP
502 postprocessors
.append({
503 'key': 'ExecAfterDownload',
504 'exec_cmd': opts
.exec_cmd
,
505 # Run this only after the files have been moved to their final locations
509 def report_args_compat(arg
, name
):
510 warnings
.append('%s given without specifying name. The arguments will be given to all %s' % (arg
, name
))
512 if 'default' in opts
.external_downloader_args
:
513 report_args_compat('--downloader-args', 'external downloaders')
515 if 'default-compat' in opts
.postprocessor_args
and 'default' not in opts
.postprocessor_args
:
516 report_args_compat('--post-processor-args', 'post-processors')
517 opts
.postprocessor_args
.setdefault('sponskrub', [])
518 opts
.postprocessor_args
['default'] = opts
.postprocessor_args
['default-compat']
521 opts
.recodevideo
if opts
.recodevideo
in FFmpegVideoConvertorPP
.SUPPORTED_EXTS
522 else opts
.remuxvideo
if opts
.remuxvideo
in FFmpegVideoRemuxerPP
.SUPPORTED_EXTS
523 else opts
.audioformat
if (opts
.extractaudio
and opts
.audioformat
!= 'best')
527 None if opts
.match_filter
is None
528 else match_filter_func(opts
.match_filter
))
531 'usenetrc': opts
.usenetrc
,
532 'username': opts
.username
,
533 'password': opts
.password
,
534 'twofactor': opts
.twofactor
,
535 'videopassword': opts
.videopassword
,
536 'ap_mso': opts
.ap_mso
,
537 'ap_username': opts
.ap_username
,
538 'ap_password': opts
.ap_password
,
539 'quiet': (opts
.quiet
or any_getting
or any_printing
),
540 'no_warnings': opts
.no_warnings
,
541 'forceurl': opts
.geturl
,
542 'forcetitle': opts
.gettitle
,
543 'forceid': opts
.getid
,
544 'forcethumbnail': opts
.getthumbnail
,
545 'forcedescription': opts
.getdescription
,
546 'forceduration': opts
.getduration
,
547 'forcefilename': opts
.getfilename
,
548 'forceformat': opts
.getformat
,
549 'forceprint': opts
.forceprint
,
550 'forcejson': opts
.dumpjson
or opts
.print_json
,
551 'dump_single_json': opts
.dump_single_json
,
552 'force_write_download_archive': opts
.force_write_download_archive
,
553 'simulate': opts
.simulate
or any_getting
,
554 'skip_download': opts
.skip_download
,
555 'format': opts
.format
,
556 'allow_unplayable_formats': opts
.allow_unplayable_formats
,
557 'ignore_no_formats_error': opts
.ignore_no_formats_error
,
558 'format_sort': opts
.format_sort
,
559 'format_sort_force': opts
.format_sort_force
,
560 'allow_multiple_video_streams': opts
.allow_multiple_video_streams
,
561 'allow_multiple_audio_streams': opts
.allow_multiple_audio_streams
,
562 'check_formats': opts
.check_formats
,
563 'listformats': opts
.listformats
,
564 'listformats_table': opts
.listformats_table
,
565 'outtmpl': opts
.outtmpl
,
566 'outtmpl_na_placeholder': opts
.outtmpl_na_placeholder
,
568 'autonumber_size': opts
.autonumber_size
,
569 'autonumber_start': opts
.autonumber_start
,
570 'restrictfilenames': opts
.restrictfilenames
,
571 'windowsfilenames': opts
.windowsfilenames
,
572 'ignoreerrors': opts
.ignoreerrors
,
573 'force_generic_extractor': opts
.force_generic_extractor
,
574 'ratelimit': opts
.ratelimit
,
575 'throttledratelimit': opts
.throttledratelimit
,
576 'overwrites': opts
.overwrites
,
577 'retries': opts
.retries
,
578 'fragment_retries': opts
.fragment_retries
,
579 'extractor_retries': opts
.extractor_retries
,
580 'skip_unavailable_fragments': opts
.skip_unavailable_fragments
,
581 'keep_fragments': opts
.keep_fragments
,
582 'concurrent_fragment_downloads': opts
.concurrent_fragment_downloads
,
583 'buffersize': opts
.buffersize
,
584 'noresizebuffer': opts
.noresizebuffer
,
585 'http_chunk_size': opts
.http_chunk_size
,
586 'continuedl': opts
.continue_dl
,
587 'noprogress': opts
.noprogress
,
588 'progress_with_newline': opts
.progress_with_newline
,
589 'playliststart': opts
.playliststart
,
590 'playlistend': opts
.playlistend
,
591 'playlistreverse': opts
.playlist_reverse
,
592 'playlistrandom': opts
.playlist_random
,
593 'noplaylist': opts
.noplaylist
,
594 'logtostderr': outtmpl_default
== '-',
595 'consoletitle': opts
.consoletitle
,
596 'nopart': opts
.nopart
,
597 'updatetime': opts
.updatetime
,
598 'writedescription': opts
.writedescription
,
599 'writeannotations': opts
.writeannotations
,
600 'writeinfojson': opts
.writeinfojson
,
601 'allow_playlist_files': opts
.allow_playlist_files
,
602 'clean_infojson': opts
.clean_infojson
,
603 'getcomments': opts
.getcomments
,
604 'writethumbnail': opts
.writethumbnail
,
605 'write_all_thumbnails': opts
.write_all_thumbnails
,
606 'writelink': opts
.writelink
,
607 'writeurllink': opts
.writeurllink
,
608 'writewebloclink': opts
.writewebloclink
,
609 'writedesktoplink': opts
.writedesktoplink
,
610 'writesubtitles': opts
.writesubtitles
,
611 'writeautomaticsub': opts
.writeautomaticsub
,
612 'allsubtitles': opts
.allsubtitles
,
613 'listsubtitles': opts
.listsubtitles
,
614 'subtitlesformat': opts
.subtitlesformat
,
615 'subtitleslangs': opts
.subtitleslangs
,
616 'matchtitle': decodeOption(opts
.matchtitle
),
617 'rejecttitle': decodeOption(opts
.rejecttitle
),
618 'max_downloads': opts
.max_downloads
,
619 'prefer_free_formats': opts
.prefer_free_formats
,
620 'trim_file_name': opts
.trim_file_name
,
621 'verbose': opts
.verbose
,
622 'dump_intermediate_pages': opts
.dump_intermediate_pages
,
623 'write_pages': opts
.write_pages
,
625 'keepvideo': opts
.keepvideo
,
626 'min_filesize': opts
.min_filesize
,
627 'max_filesize': opts
.max_filesize
,
628 'min_views': opts
.min_views
,
629 'max_views': opts
.max_views
,
631 'cachedir': opts
.cachedir
,
632 'youtube_print_sig_code': opts
.youtube_print_sig_code
,
633 'age_limit': opts
.age_limit
,
634 'download_archive': download_archive_fn
,
635 'break_on_existing': opts
.break_on_existing
,
636 'break_on_reject': opts
.break_on_reject
,
637 'skip_playlist_after_errors': opts
.skip_playlist_after_errors
,
638 'cookiefile': opts
.cookiefile
,
639 'cookiesfrombrowser': opts
.cookiesfrombrowser
,
640 'nocheckcertificate': opts
.no_check_certificate
,
641 'prefer_insecure': opts
.prefer_insecure
,
643 'socket_timeout': opts
.socket_timeout
,
644 'bidi_workaround': opts
.bidi_workaround
,
645 'debug_printtraffic': opts
.debug_printtraffic
,
646 'prefer_ffmpeg': opts
.prefer_ffmpeg
,
647 'include_ads': opts
.include_ads
,
648 'default_search': opts
.default_search
,
649 'dynamic_mpd': opts
.dynamic_mpd
,
650 'extractor_args': opts
.extractor_args
,
651 'youtube_include_dash_manifest': opts
.youtube_include_dash_manifest
,
652 'youtube_include_hls_manifest': opts
.youtube_include_hls_manifest
,
653 'encoding': opts
.encoding
,
654 'extract_flat': opts
.extract_flat
,
655 'mark_watched': opts
.mark_watched
,
656 'merge_output_format': opts
.merge_output_format
,
657 'final_ext': final_ext
,
658 'postprocessors': postprocessors
,
660 'source_address': opts
.source_address
,
661 'call_home': opts
.call_home
,
662 'sleep_interval_requests': opts
.sleep_interval_requests
,
663 'sleep_interval': opts
.sleep_interval
,
664 'max_sleep_interval': opts
.max_sleep_interval
,
665 'sleep_interval_subtitles': opts
.sleep_interval_subtitles
,
666 'external_downloader': opts
.external_downloader
,
667 'list_thumbnails': opts
.list_thumbnails
,
668 'playlist_items': opts
.playlist_items
,
669 'xattr_set_filesize': opts
.xattr_set_filesize
,
670 'match_filter': match_filter
,
671 'no_color': opts
.no_color
,
672 'ffmpeg_location': opts
.ffmpeg_location
,
673 'hls_prefer_native': opts
.hls_prefer_native
,
674 'hls_use_mpegts': opts
.hls_use_mpegts
,
675 'hls_split_discontinuity': opts
.hls_split_discontinuity
,
676 'external_downloader_args': opts
.external_downloader_args
,
677 'postprocessor_args': opts
.postprocessor_args
,
678 'cn_verification_proxy': opts
.cn_verification_proxy
,
679 'geo_verification_proxy': opts
.geo_verification_proxy
,
680 'geo_bypass': opts
.geo_bypass
,
681 'geo_bypass_country': opts
.geo_bypass_country
,
682 'geo_bypass_ip_block': opts
.geo_bypass_ip_block
,
683 'warnings': warnings
,
684 'compat_opts': compat_opts
,
685 # just for deprecation check
686 'autonumber': opts
.autonumber
or None,
687 'usetitle': opts
.usetitle
or None,
688 'useid': opts
.useid
or None,
691 with YoutubeDL(ydl_opts
) as ydl
:
692 actual_use
= len(all_urls
) or opts
.load_info_filename
700 # If updater returns True, exit. Required for windows
703 sys
.exit('ERROR: The program must exit for the update to complete')
708 if opts
.update_self
or opts
.rm_cachedir
:
711 ydl
.warn_if_short_id(sys
.argv
[1:] if argv
is None else argv
)
713 'You must provide at least one URL.\n'
714 'Type yt-dlp --help to see a list of all options.')
717 if opts
.load_info_filename
is not None:
718 retcode
= ydl
.download_with_info_file(expand_path(opts
.load_info_filename
))
720 retcode
= ydl
.download(all_urls
)
721 except (MaxDownloadsReached
, ExistingVideoReached
, RejectedVideoReached
):
722 ydl
.to_screen('Aborting remaining downloads')
731 except DownloadError
:
733 except SameFileError
:
734 sys
.exit('ERROR: fixed output name but more than one file to download')
735 except KeyboardInterrupt:
736 sys
.exit('\nERROR: Interrupted by user')
737 except BrokenPipeError
:
738 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
739 devnull
= os
.open(os
.devnull
, os
.O_WRONLY
)
740 os
.dup2(devnull
, sys
.stdout
.fileno())
741 sys
.exit(r
'\nERROR: {err}')
744 __all__
= ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']