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