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 (
23 workaround_optparse_bug9161
,
25 from .cookies
import SUPPORTED_BROWSERS
46 from .update
import run_update
47 from .downloader
import (
50 from .extractor
import gen_extractors
, list_extractors
51 from .extractor
.common
import InfoExtractor
52 from .extractor
.adobepass
import MSO_INFO
53 from .postprocessor
import (
55 FFmpegSubtitlesConvertorPP
,
56 FFmpegThumbnailsConvertorPP
,
57 FFmpegVideoConvertorPP
,
62 from .YoutubeDL
import YoutubeDL
65 def _real_main(argv
=None):
66 # Compatibility fixes for Windows
67 if sys
.platform
== 'win32':
68 # https://github.com/ytdl-org/youtube-dl/issues/820
69 codecs
.register(lambda name
: codecs
.lookup('utf-8') if name
== 'cp65001' else None)
71 workaround_optparse_bug9161()
73 setproctitle('yt-dlp')
75 parser
, opts
, args
= parseOpts(argv
)
76 warnings
, deprecation_warnings
= [], []
79 if opts
.user_agent
is not None:
80 std_headers
['User-Agent'] = opts
.user_agent
83 if opts
.referer
is not None:
84 std_headers
['Referer'] = opts
.referer
87 std_headers
.update(opts
.headers
)
90 if opts
.dump_user_agent
:
91 write_string(std_headers
['User-Agent'] + '\n', out
=sys
.stdout
)
94 # Batch file verification
96 if opts
.batchfile
is not None:
98 if opts
.batchfile
== '-':
99 write_string('Reading URLs from stdin - EOF (%s) to end:\n' % (
100 'Ctrl+Z' if compat_os_name
== 'nt' else 'Ctrl+D'))
104 expand_path(opts
.batchfile
),
105 'r', encoding
='utf-8', errors
='ignore')
106 batch_urls
= read_batch_urls(batchfd
)
108 write_string('[debug] Batch file urls: ' + repr(batch_urls
) + '\n')
110 sys
.exit('ERROR: batch file %s could not be read' % opts
.batchfile
)
111 all_urls
= batch_urls
+ [url
.strip() for url
in args
] # batch_urls are already striped in read_batch_urls
112 _enc
= preferredencoding()
113 all_urls
= [url
.decode(_enc
, 'ignore') if isinstance(url
, bytes) else url
for url
in all_urls
]
115 if opts
.list_extractors
:
116 for ie
in list_extractors(opts
.age_limit
):
117 write_string(ie
.IE_NAME
+ (' (CURRENTLY BROKEN)' if not ie
.working() else '') + '\n', out
=sys
.stdout
)
118 matchedUrls
= [url
for url
in all_urls
if ie
.suitable(url
)]
119 for mu
in matchedUrls
:
120 write_string(' ' + mu
+ '\n', out
=sys
.stdout
)
122 if opts
.list_extractor_descriptions
:
123 for ie
in list_extractors(opts
.age_limit
):
126 desc
= getattr(ie
, 'IE_DESC', ie
.IE_NAME
)
129 if getattr(ie
, 'SEARCH_KEY', None) is not None:
130 _SEARCHES
= ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
131 _COUNTS
= ('', '5', '10', 'all')
132 desc
+= f
'; "{ie.SEARCH_KEY}:" prefix (Example: "{ie.SEARCH_KEY}{random.choice(_COUNTS)}:{random.choice(_SEARCHES)}")'
133 write_string(desc
+ '\n', out
=sys
.stdout
)
136 table
= [[mso_id
, mso_info
['name']] for mso_id
, mso_info
in MSO_INFO
.items()]
137 write_string('Supported TV Providers:\n' + render_table(['mso', 'mso name'], table
) + '\n', out
=sys
.stdout
)
140 # Conflicting, missing and erroneous options
141 if opts
.format
== 'best':
142 warnings
.append('.\n '.join((
143 '"-f best" selects the best pre-merged format which is often not the best option',
144 'To let yt-dlp download and merge the best available formats, simply do not pass any format selection',
145 'If you know what you are doing and want only the best pre-merged format, use "-f b" instead to suppress this warning')))
146 if opts
.usenetrc
and (opts
.username
is not None or opts
.password
is not None):
147 parser
.error('using .netrc conflicts with giving username/password')
148 if opts
.password
is not None and opts
.username
is None:
149 parser
.error('account username missing\n')
150 if opts
.ap_password
is not None and opts
.ap_username
is None:
151 parser
.error('TV Provider account username missing\n')
152 if opts
.autonumber_size
is not None:
153 if opts
.autonumber_size
<= 0:
154 parser
.error('auto number size must be positive')
155 if opts
.autonumber_start
is not None:
156 if opts
.autonumber_start
< 0:
157 parser
.error('auto number start must be positive or 0')
158 if opts
.username
is not None and opts
.password
is None:
159 opts
.password
= compat_getpass('Type account password and press [Return]: ')
160 if opts
.ap_username
is not None and opts
.ap_password
is None:
161 opts
.ap_password
= compat_getpass('Type TV provider account password and press [Return]: ')
162 if opts
.ratelimit
is not None:
163 numeric_limit
= FileDownloader
.parse_bytes(opts
.ratelimit
)
164 if numeric_limit
is None:
165 parser
.error('invalid rate limit specified')
166 opts
.ratelimit
= numeric_limit
167 if opts
.throttledratelimit
is not None:
168 numeric_limit
= FileDownloader
.parse_bytes(opts
.throttledratelimit
)
169 if numeric_limit
is None:
170 parser
.error('invalid rate limit specified')
171 opts
.throttledratelimit
= numeric_limit
172 if opts
.min_filesize
is not None:
173 numeric_limit
= FileDownloader
.parse_bytes(opts
.min_filesize
)
174 if numeric_limit
is None:
175 parser
.error('invalid min_filesize specified')
176 opts
.min_filesize
= numeric_limit
177 if opts
.max_filesize
is not None:
178 numeric_limit
= FileDownloader
.parse_bytes(opts
.max_filesize
)
179 if numeric_limit
is None:
180 parser
.error('invalid max_filesize specified')
181 opts
.max_filesize
= numeric_limit
182 if opts
.sleep_interval
is not None:
183 if opts
.sleep_interval
< 0:
184 parser
.error('sleep interval must be positive or 0')
185 if opts
.max_sleep_interval
is not None:
186 if opts
.max_sleep_interval
< 0:
187 parser
.error('max sleep interval must be positive or 0')
188 if opts
.sleep_interval
is None:
189 parser
.error('min sleep interval must be specified, use --min-sleep-interval')
190 if opts
.max_sleep_interval
< opts
.sleep_interval
:
191 parser
.error('max sleep interval must be greater than or equal to min sleep interval')
193 opts
.max_sleep_interval
= opts
.sleep_interval
194 if opts
.sleep_interval_subtitles
is not None:
195 if opts
.sleep_interval_subtitles
< 0:
196 parser
.error('subtitles sleep interval must be positive or 0')
197 if opts
.sleep_interval_requests
is not None:
198 if opts
.sleep_interval_requests
< 0:
199 parser
.error('requests sleep interval must be positive or 0')
200 if opts
.ap_mso
and opts
.ap_mso
not in MSO_INFO
:
201 parser
.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers')
202 if opts
.overwrites
: # --yes-overwrites implies --no-continue
203 opts
.continue_dl
= False
204 if opts
.concurrent_fragment_downloads
<= 0:
205 parser
.error('Concurrent fragments must be positive')
206 if opts
.wait_for_video
is not None:
207 min_wait
, max_wait
, *_
= map(parse_duration
, opts
.wait_for_video
.split('-', 1) + [None])
208 if min_wait
is None or (max_wait
is None and '-' in opts
.wait_for_video
):
209 parser
.error('Invalid time range to wait')
210 elif max_wait
is not None and max_wait
< min_wait
:
211 parser
.error('Minimum time range to wait must not be longer than the maximum')
212 opts
.wait_for_video
= (min_wait
, max_wait
)
214 def parse_retries(retries
, name
=''):
215 if retries
in ('inf', 'infinite'):
216 parsed_retries
= float('inf')
219 parsed_retries
= int(retries
)
220 except (TypeError, ValueError):
221 parser
.error('invalid %sretry count specified' % name
)
222 return parsed_retries
223 if opts
.retries
is not None:
224 opts
.retries
= parse_retries(opts
.retries
)
225 if opts
.file_access_retries
is not None:
226 opts
.file_access_retries
= parse_retries(opts
.file_access_retries
, 'file access ')
227 if opts
.fragment_retries
is not None:
228 opts
.fragment_retries
= parse_retries(opts
.fragment_retries
, 'fragment ')
229 if opts
.extractor_retries
is not None:
230 opts
.extractor_retries
= parse_retries(opts
.extractor_retries
, 'extractor ')
231 if opts
.buffersize
is not None:
232 numeric_buffersize
= FileDownloader
.parse_bytes(opts
.buffersize
)
233 if numeric_buffersize
is None:
234 parser
.error('invalid buffer size specified')
235 opts
.buffersize
= numeric_buffersize
236 if opts
.http_chunk_size
is not None:
237 numeric_chunksize
= FileDownloader
.parse_bytes(opts
.http_chunk_size
)
238 if not numeric_chunksize
:
239 parser
.error('invalid http chunk size specified')
240 opts
.http_chunk_size
= numeric_chunksize
241 if opts
.playliststart
<= 0:
242 raise parser
.error('Playlist start must be positive')
243 if opts
.playlistend
not in (-1, None) and opts
.playlistend
< opts
.playliststart
:
244 raise parser
.error('Playlist end must be greater than playlist start')
245 if opts
.extractaudio
:
246 opts
.audioformat
= opts
.audioformat
.lower()
247 if opts
.audioformat
not in ['best'] + list(FFmpegExtractAudioPP
.SUPPORTED_EXTS
):
248 parser
.error('invalid audio format specified')
249 if opts
.audioquality
:
250 opts
.audioquality
= opts
.audioquality
.strip('k').strip('K')
251 audioquality
= int_or_none(float_or_none(opts
.audioquality
)) # int_or_none prevents inf, nan
252 if audioquality
is None or audioquality
< 0:
253 parser
.error('invalid audio quality specified')
254 if opts
.recodevideo
is not None:
255 opts
.recodevideo
= opts
.recodevideo
.replace(' ', '')
256 if not re
.match(FFmpegVideoConvertorPP
.FORMAT_RE
, opts
.recodevideo
):
257 parser
.error('invalid video remux format specified')
258 if opts
.remuxvideo
is not None:
259 opts
.remuxvideo
= opts
.remuxvideo
.replace(' ', '')
260 if not re
.match(FFmpegVideoRemuxerPP
.FORMAT_RE
, opts
.remuxvideo
):
261 parser
.error('invalid video remux format specified')
262 if opts
.convertsubtitles
is not None:
263 if opts
.convertsubtitles
not in FFmpegSubtitlesConvertorPP
.SUPPORTED_EXTS
:
264 parser
.error('invalid subtitle format specified')
265 if opts
.convertthumbnails
is not None:
266 if opts
.convertthumbnails
not in FFmpegThumbnailsConvertorPP
.SUPPORTED_EXTS
:
267 parser
.error('invalid thumbnail format specified')
268 if opts
.cookiesfrombrowser
is not None:
269 opts
.cookiesfrombrowser
= [
270 part
.strip() or None for part
in opts
.cookiesfrombrowser
.split(':', 1)]
271 if opts
.cookiesfrombrowser
[0].lower() not in SUPPORTED_BROWSERS
:
272 parser
.error('unsupported browser specified for cookies')
273 geo_bypass_code
= opts
.geo_bypass_ip_block
or opts
.geo_bypass_country
274 if geo_bypass_code
is not None:
276 GeoUtils
.random_ipv4(geo_bypass_code
)
278 parser
.error('unsupported geo-bypass country or ip-block')
280 if opts
.date
is not None:
281 date
= DateRange
.day(opts
.date
)
283 date
= DateRange(opts
.dateafter
, opts
.datebefore
)
285 compat_opts
= opts
.compat_opts
287 def report_conflict(arg1
, arg2
):
288 warnings
.append(f
'{arg2} is ignored since {arg1} was given')
290 def _unused_compat_opt(name
):
291 if name
not in compat_opts
:
293 compat_opts
.discard(name
)
294 compat_opts
.update(['*%s' % name
])
297 def set_default_compat(compat_name
, opt_name
, default
=True, remove_compat
=True):
298 attr
= getattr(opts
, opt_name
)
299 if compat_name
in compat_opts
:
301 setattr(opts
, opt_name
, not default
)
305 _unused_compat_opt(compat_name
)
308 setattr(opts
, opt_name
, default
)
311 set_default_compat('abort-on-error', 'ignoreerrors', 'only_download')
312 set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
313 set_default_compat('no-clean-infojson', 'clean_infojson')
314 if 'no-attach-info-json' in compat_opts
:
315 if opts
.embed_infojson
:
316 _unused_compat_opt('no-attach-info-json')
318 opts
.embed_infojson
= False
319 if 'format-sort' in compat_opts
:
320 opts
.format_sort
.extend(InfoExtractor
.FormatSort
.ytdl_default
)
321 _video_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat
=False)
322 _audio_multistreams_set
= set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat
=False)
323 if _video_multistreams_set
is False and _audio_multistreams_set
is False:
324 _unused_compat_opt('multistreams')
325 outtmpl_default
= opts
.outtmpl
.get('default')
327 if outtmpl_default
is None:
328 outtmpl_default
= opts
.outtmpl
['default'] = '%(id)s.%(ext)s'
330 report_conflict('--output', '--id')
331 if 'filename' in compat_opts
:
332 if outtmpl_default
is None:
333 outtmpl_default
= opts
.outtmpl
['default'] = '%(title)s-%(id)s.%(ext)s'
335 _unused_compat_opt('filename')
337 def validate_outtmpl(tmpl
, msg
):
338 err
= YoutubeDL
.validate_outtmpl(tmpl
)
340 parser
.error('invalid %s %r: %s' % (msg
, tmpl
, error_to_compat_str(err
)))
342 for k
, tmpl
in opts
.outtmpl
.items():
343 validate_outtmpl(tmpl
, f
'{k} output template')
344 opts
.forceprint
= opts
.forceprint
or []
345 for tmpl
in opts
.forceprint
or []:
346 validate_outtmpl(tmpl
, 'print template')
347 validate_outtmpl(opts
.sponsorblock_chapter_title
, 'SponsorBlock chapter title')
348 for k
, tmpl
in opts
.progress_template
.items():
349 k
= f
'{k[:-6]} console title' if '-title' in k
else f
'{k} progress'
350 validate_outtmpl(tmpl
, f
'{k} template')
352 if opts
.extractaudio
and not opts
.keepvideo
and opts
.format
is None:
353 opts
.format
= 'bestaudio/best'
355 if outtmpl_default
is not None and not os
.path
.splitext(outtmpl_default
)[1] and opts
.extractaudio
:
356 parser
.error('Cannot download a video and extract audio into the same'
357 ' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
358 ' template'.format(outtmpl_default
))
360 for f
in opts
.format_sort
:
361 if re
.match(InfoExtractor
.FormatSort
.regex
, f
) is None:
362 parser
.error('invalid format sort string "%s" specified' % f
)
364 def metadataparser_actions(f
):
365 if isinstance(f
, str):
366 cmd
= '--parse-metadata %s' % compat_shlex_quote(f
)
368 actions
= [MetadataFromFieldPP
.to_action(f
)]
369 except Exception as err
:
370 parser
.error(f
'{cmd} is invalid; {err}')
372 cmd
= '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote
, f
))
373 actions
= ((MetadataParserPP
.Actions
.REPLACE
, x
, *f
[1:]) for x
in f
[0].split(','))
375 for action
in actions
:
377 MetadataParserPP
.validate_action(*action
)
378 except Exception as err
:
379 parser
.error(f
'{cmd} is invalid; {err}')
382 if opts
.parse_metadata
is None:
383 opts
.parse_metadata
= []
384 if opts
.metafromtitle
is not None:
385 opts
.parse_metadata
.append('title:%s' % opts
.metafromtitle
)
386 opts
.parse_metadata
= list(itertools
.chain(*map(metadataparser_actions
, opts
.parse_metadata
)))
388 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
389 any_printing
= opts
.print_json
390 download_archive_fn
= expand_path(opts
.download_archive
) if opts
.download_archive
is not None else opts
.download_archive
392 # If JSON is not printed anywhere, but comments are requested, save it to file
393 printing_json
= opts
.dumpjson
or opts
.print_json
or opts
.dump_single_json
394 if opts
.getcomments
and not printing_json
:
395 opts
.writeinfojson
= True
397 if opts
.no_sponsorblock
:
398 opts
.sponsorblock_mark
= set()
399 opts
.sponsorblock_remove
= set()
400 sponsorblock_query
= opts
.sponsorblock_mark | opts
.sponsorblock_remove
402 opts
.remove_chapters
= opts
.remove_chapters
or []
404 if (opts
.remove_chapters
or sponsorblock_query
) and opts
.sponskrub
is not False:
406 if opts
.remove_chapters
:
407 report_conflict('--remove-chapters', '--sponskrub')
408 if opts
.sponsorblock_mark
:
409 report_conflict('--sponsorblock-mark', '--sponskrub')
410 if opts
.sponsorblock_remove
:
411 report_conflict('--sponsorblock-remove', '--sponskrub')
412 opts
.sponskrub
= False
413 if opts
.sponskrub_cut
and opts
.split_chapters
and opts
.sponskrub
is not False:
414 report_conflict('--split-chapter', '--sponskrub-cut')
415 opts
.sponskrub_cut
= False
417 if opts
.remuxvideo
and opts
.recodevideo
:
418 report_conflict('--recode-video', '--remux-video')
419 opts
.remuxvideo
= False
421 if opts
.allow_unplayable_formats
:
422 def report_unplayable_conflict(opt_name
, arg
, default
=False, allowed
=None):
423 val
= getattr(opts
, opt_name
)
424 if (not allowed
and val
) or (allowed
and not allowed(val
)):
425 report_conflict('--allow-unplayable-formats', arg
)
426 setattr(opts
, opt_name
, default
)
428 report_unplayable_conflict('extractaudio', '--extract-audio')
429 report_unplayable_conflict('remuxvideo', '--remux-video')
430 report_unplayable_conflict('recodevideo', '--recode-video')
431 report_unplayable_conflict('addmetadata', '--embed-metadata')
432 report_unplayable_conflict('addchapters', '--embed-chapters')
433 report_unplayable_conflict('embed_infojson', '--embed-info-json')
434 opts
.embed_infojson
= False
435 report_unplayable_conflict('embedsubtitles', '--embed-subs')
436 report_unplayable_conflict('embedthumbnail', '--embed-thumbnail')
437 report_unplayable_conflict('xattrs', '--xattrs')
438 report_unplayable_conflict('fixup', '--fixup', default
='never', allowed
=lambda x
: x
in (None, 'never', 'ignore'))
440 report_unplayable_conflict('remove_chapters', '--remove-chapters', default
=[])
441 report_unplayable_conflict('sponsorblock_remove', '--sponsorblock-remove', default
=set())
442 report_unplayable_conflict('sponskrub', '--sponskrub', default
=set())
443 opts
.sponskrub
= False
445 if (opts
.addmetadata
or opts
.sponsorblock_mark
) and opts
.addchapters
is None:
446 opts
.addchapters
= True
449 postprocessors
= list(opts
.add_postprocessors
)
450 if sponsorblock_query
:
451 postprocessors
.append({
452 'key': 'SponsorBlock',
453 'categories': sponsorblock_query
,
454 'api': opts
.sponsorblock_api
,
455 # Run this immediately after extraction is complete
456 'when': 'pre_process'
458 if opts
.parse_metadata
:
459 postprocessors
.append({
460 'key': 'MetadataParser',
461 'actions': opts
.parse_metadata
,
462 # Run this immediately after extraction is complete
463 'when': 'pre_process'
465 if opts
.convertsubtitles
:
466 postprocessors
.append({
467 'key': 'FFmpegSubtitlesConvertor',
468 'format': opts
.convertsubtitles
,
469 # Run this before the actual video download
472 if opts
.convertthumbnails
:
473 postprocessors
.append({
474 'key': 'FFmpegThumbnailsConvertor',
475 'format': opts
.convertthumbnails
,
476 # Run this before the actual video download
479 # Must be after all other before_dl
480 if opts
.exec_before_dl_cmd
:
481 postprocessors
.append({
483 'exec_cmd': opts
.exec_before_dl_cmd
,
486 if opts
.extractaudio
:
487 postprocessors
.append({
488 'key': 'FFmpegExtractAudio',
489 'preferredcodec': opts
.audioformat
,
490 'preferredquality': opts
.audioquality
,
491 'nopostoverwrites': opts
.nopostoverwrites
,
494 postprocessors
.append({
495 'key': 'FFmpegVideoRemuxer',
496 'preferedformat': opts
.remuxvideo
,
499 postprocessors
.append({
500 'key': 'FFmpegVideoConvertor',
501 'preferedformat': opts
.recodevideo
,
503 # If ModifyChapters is going to remove chapters, subtitles must already be in the container.
504 if opts
.embedsubtitles
:
505 already_have_subtitle
= opts
.writesubtitles
and 'no-keep-subs' not in compat_opts
506 postprocessors
.append({
507 'key': 'FFmpegEmbedSubtitle',
508 # already_have_subtitle = True prevents the file from being deleted after embedding
509 'already_have_subtitle': already_have_subtitle
511 if not opts
.writeautomaticsub
and 'no-keep-subs' not in compat_opts
:
512 opts
.writesubtitles
= True
513 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
514 # this was the old behaviour if only --all-sub was given.
515 if opts
.allsubtitles
and not opts
.writeautomaticsub
:
516 opts
.writesubtitles
= True
517 # ModifyChapters must run before FFmpegMetadataPP
518 remove_chapters_patterns
, remove_ranges
= [], []
519 for regex
in opts
.remove_chapters
:
520 if regex
.startswith('*'):
521 dur
= list(map(parse_duration
, regex
[1:].split('-')))
522 if len(dur
) == 2 and all(t
is not None for t
in dur
):
523 remove_ranges
.append(tuple(dur
))
525 parser
.error(f
'invalid --remove-chapters time range {regex!r}. Must be of the form *start-end')
527 remove_chapters_patterns
.append(re
.compile(regex
))
528 except re
.error
as err
:
529 parser
.error(f
'invalid --remove-chapters regex {regex!r} - {err}')
530 if opts
.remove_chapters
or sponsorblock_query
:
531 postprocessors
.append({
532 'key': 'ModifyChapters',
533 'remove_chapters_patterns': remove_chapters_patterns
,
534 'remove_sponsor_segments': opts
.sponsorblock_remove
,
535 'remove_ranges': remove_ranges
,
536 'sponsorblock_chapter_title': opts
.sponsorblock_chapter_title
,
537 'force_keyframes': opts
.force_keyframes_at_cuts
539 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
540 # FFmpegExtractAudioPP as containers before conversion may not support
541 # metadata (3gp, webm, etc.)
542 # By default ffmpeg preserves metadata applicable for both
543 # source and target containers. From this point the container won't change,
544 # so metadata can be added here.
545 if opts
.addmetadata
or opts
.addchapters
or opts
.embed_infojson
:
546 if opts
.embed_infojson
is None:
547 opts
.embed_infojson
= 'if_exists'
548 postprocessors
.append({
549 'key': 'FFmpegMetadata',
550 'add_chapters': opts
.addchapters
,
551 'add_metadata': opts
.addmetadata
,
552 'add_infojson': opts
.embed_infojson
,
555 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
556 # but must be below EmbedSubtitle and FFmpegMetadata
557 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
558 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
559 if opts
.sponskrub
is not False:
560 postprocessors
.append({
562 'path': opts
.sponskrub_path
,
563 'args': opts
.sponskrub_args
,
564 'cut': opts
.sponskrub_cut
,
565 'force': opts
.sponskrub_force
,
566 'ignoreerror': opts
.sponskrub
is None,
569 if opts
.embedthumbnail
:
570 postprocessors
.append({
571 'key': 'EmbedThumbnail',
572 # already_have_thumbnail = True prevents the file from being deleted after embedding
573 'already_have_thumbnail': opts
.writethumbnail
575 if not opts
.writethumbnail
:
576 opts
.writethumbnail
= True
577 opts
.outtmpl
['pl_thumbnail'] = ''
578 if opts
.split_chapters
:
579 postprocessors
.append({
580 'key': 'FFmpegSplitChapters',
581 'force_keyframes': opts
.force_keyframes_at_cuts
,
583 # XAttrMetadataPP should be run after post-processors that may change file contents
585 postprocessors
.append({'key': 'XAttrMetadata'}
)
586 # Exec must be the last PP
588 postprocessors
.append({
590 'exec_cmd': opts
.exec_cmd
,
591 # Run this only after the files have been moved to their final locations
595 def report_args_compat(arg
, name
):
596 warnings
.append('%s given without specifying name. The arguments will be given to all %s' % (arg
, name
))
598 if 'default' in opts
.external_downloader_args
:
599 report_args_compat('--downloader-args', 'external downloaders')
601 if 'default-compat' in opts
.postprocessor_args
and 'default' not in opts
.postprocessor_args
:
602 report_args_compat('--post-processor-args', 'post-processors')
603 opts
.postprocessor_args
.setdefault('sponskrub', [])
604 opts
.postprocessor_args
['default'] = opts
.postprocessor_args
['default-compat']
606 def report_deprecation(val
, old
, new
=None):
609 deprecation_warnings
.append(
610 f
'{old} is deprecated and may be removed in a future version. Use {new} instead' if new
611 else f
'{old} is deprecated and may not work as expected')
613 report_deprecation(opts
.sponskrub
, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove')
614 report_deprecation(not opts
.prefer_ffmpeg
, '--prefer-avconv', 'ffmpeg')
615 report_deprecation(opts
.include_ads
, '--include-ads')
616 # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future
617 # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it
620 opts
.recodevideo
if opts
.recodevideo
in FFmpegVideoConvertorPP
.SUPPORTED_EXTS
621 else opts
.remuxvideo
if opts
.remuxvideo
in FFmpegVideoRemuxerPP
.SUPPORTED_EXTS
622 else opts
.audioformat
if (opts
.extractaudio
and opts
.audioformat
!= 'best')
626 None if opts
.match_filter
is None
627 else match_filter_func(opts
.match_filter
))
630 'usenetrc': opts
.usenetrc
,
631 'netrc_location': opts
.netrc_location
,
632 'username': opts
.username
,
633 'password': opts
.password
,
634 'twofactor': opts
.twofactor
,
635 'videopassword': opts
.videopassword
,
636 'ap_mso': opts
.ap_mso
,
637 'ap_username': opts
.ap_username
,
638 'ap_password': opts
.ap_password
,
639 'quiet': (opts
.quiet
or any_getting
or any_printing
),
640 'no_warnings': opts
.no_warnings
,
641 'forceurl': opts
.geturl
,
642 'forcetitle': opts
.gettitle
,
643 'forceid': opts
.getid
,
644 'forcethumbnail': opts
.getthumbnail
,
645 'forcedescription': opts
.getdescription
,
646 'forceduration': opts
.getduration
,
647 'forcefilename': opts
.getfilename
,
648 'forceformat': opts
.getformat
,
649 'forceprint': opts
.forceprint
,
650 'forcejson': opts
.dumpjson
or opts
.print_json
,
651 'dump_single_json': opts
.dump_single_json
,
652 'force_write_download_archive': opts
.force_write_download_archive
,
653 'simulate': (any_getting
or None) if opts
.simulate
is None else opts
.simulate
,
654 'skip_download': opts
.skip_download
,
655 'format': opts
.format
,
656 'allow_unplayable_formats': opts
.allow_unplayable_formats
,
657 'ignore_no_formats_error': opts
.ignore_no_formats_error
,
658 'format_sort': opts
.format_sort
,
659 'format_sort_force': opts
.format_sort_force
,
660 'allow_multiple_video_streams': opts
.allow_multiple_video_streams
,
661 'allow_multiple_audio_streams': opts
.allow_multiple_audio_streams
,
662 'check_formats': opts
.check_formats
,
663 'listformats': opts
.listformats
,
664 'listformats_table': opts
.listformats_table
,
665 'outtmpl': opts
.outtmpl
,
666 'outtmpl_na_placeholder': opts
.outtmpl_na_placeholder
,
668 'autonumber_size': opts
.autonumber_size
,
669 'autonumber_start': opts
.autonumber_start
,
670 'restrictfilenames': opts
.restrictfilenames
,
671 'windowsfilenames': opts
.windowsfilenames
,
672 'ignoreerrors': opts
.ignoreerrors
,
673 'force_generic_extractor': opts
.force_generic_extractor
,
674 'ratelimit': opts
.ratelimit
,
675 'throttledratelimit': opts
.throttledratelimit
,
676 'overwrites': opts
.overwrites
,
677 'retries': opts
.retries
,
678 'file_access_retries': opts
.file_access_retries
,
679 'fragment_retries': opts
.fragment_retries
,
680 'extractor_retries': opts
.extractor_retries
,
681 'skip_unavailable_fragments': opts
.skip_unavailable_fragments
,
682 'keep_fragments': opts
.keep_fragments
,
683 'concurrent_fragment_downloads': opts
.concurrent_fragment_downloads
,
684 'buffersize': opts
.buffersize
,
685 'noresizebuffer': opts
.noresizebuffer
,
686 'http_chunk_size': opts
.http_chunk_size
,
687 'continuedl': opts
.continue_dl
,
688 'noprogress': opts
.quiet
if opts
.noprogress
is None else opts
.noprogress
,
689 'progress_with_newline': opts
.progress_with_newline
,
690 'progress_template': opts
.progress_template
,
691 'playliststart': opts
.playliststart
,
692 'playlistend': opts
.playlistend
,
693 'playlistreverse': opts
.playlist_reverse
,
694 'playlistrandom': opts
.playlist_random
,
695 'noplaylist': opts
.noplaylist
,
696 'logtostderr': outtmpl_default
== '-',
697 'consoletitle': opts
.consoletitle
,
698 'nopart': opts
.nopart
,
699 'updatetime': opts
.updatetime
,
700 'writedescription': opts
.writedescription
,
701 'writeannotations': opts
.writeannotations
,
702 'writeinfojson': opts
.writeinfojson
,
703 'allow_playlist_files': opts
.allow_playlist_files
,
704 'clean_infojson': opts
.clean_infojson
,
705 'getcomments': opts
.getcomments
,
706 'writethumbnail': opts
.writethumbnail
is True,
707 'write_all_thumbnails': opts
.writethumbnail
== 'all',
708 'writelink': opts
.writelink
,
709 'writeurllink': opts
.writeurllink
,
710 'writewebloclink': opts
.writewebloclink
,
711 'writedesktoplink': opts
.writedesktoplink
,
712 'writesubtitles': opts
.writesubtitles
,
713 'writeautomaticsub': opts
.writeautomaticsub
,
714 'allsubtitles': opts
.allsubtitles
,
715 'listsubtitles': opts
.listsubtitles
,
716 'subtitlesformat': opts
.subtitlesformat
,
717 'subtitleslangs': opts
.subtitleslangs
,
718 'matchtitle': decodeOption(opts
.matchtitle
),
719 'rejecttitle': decodeOption(opts
.rejecttitle
),
720 'max_downloads': opts
.max_downloads
,
721 'prefer_free_formats': opts
.prefer_free_formats
,
722 'trim_file_name': opts
.trim_file_name
,
723 'verbose': opts
.verbose
,
724 'dump_intermediate_pages': opts
.dump_intermediate_pages
,
725 'write_pages': opts
.write_pages
,
727 'keepvideo': opts
.keepvideo
,
728 'min_filesize': opts
.min_filesize
,
729 'max_filesize': opts
.max_filesize
,
730 'min_views': opts
.min_views
,
731 'max_views': opts
.max_views
,
733 'cachedir': opts
.cachedir
,
734 'youtube_print_sig_code': opts
.youtube_print_sig_code
,
735 'age_limit': opts
.age_limit
,
736 'download_archive': download_archive_fn
,
737 'break_on_existing': opts
.break_on_existing
,
738 'break_on_reject': opts
.break_on_reject
,
739 'break_per_url': opts
.break_per_url
,
740 'skip_playlist_after_errors': opts
.skip_playlist_after_errors
,
741 'cookiefile': opts
.cookiefile
,
742 'cookiesfrombrowser': opts
.cookiesfrombrowser
,
743 'nocheckcertificate': opts
.no_check_certificate
,
744 'prefer_insecure': opts
.prefer_insecure
,
746 'socket_timeout': opts
.socket_timeout
,
747 'bidi_workaround': opts
.bidi_workaround
,
748 'debug_printtraffic': opts
.debug_printtraffic
,
749 'prefer_ffmpeg': opts
.prefer_ffmpeg
,
750 'include_ads': opts
.include_ads
,
751 'default_search': opts
.default_search
,
752 'dynamic_mpd': opts
.dynamic_mpd
,
753 'extractor_args': opts
.extractor_args
,
754 'youtube_include_dash_manifest': opts
.youtube_include_dash_manifest
,
755 'youtube_include_hls_manifest': opts
.youtube_include_hls_manifest
,
756 'encoding': opts
.encoding
,
757 'extract_flat': opts
.extract_flat
,
758 'live_from_start': opts
.live_from_start
,
759 'wait_for_video': opts
.wait_for_video
,
760 'mark_watched': opts
.mark_watched
,
761 'merge_output_format': opts
.merge_output_format
,
762 'final_ext': final_ext
,
763 'postprocessors': postprocessors
,
765 'source_address': opts
.source_address
,
766 'call_home': opts
.call_home
,
767 'sleep_interval_requests': opts
.sleep_interval_requests
,
768 'sleep_interval': opts
.sleep_interval
,
769 'max_sleep_interval': opts
.max_sleep_interval
,
770 'sleep_interval_subtitles': opts
.sleep_interval_subtitles
,
771 'external_downloader': opts
.external_downloader
,
772 'list_thumbnails': opts
.list_thumbnails
,
773 'playlist_items': opts
.playlist_items
,
774 'xattr_set_filesize': opts
.xattr_set_filesize
,
775 'match_filter': match_filter
,
776 'no_color': opts
.no_color
,
777 'ffmpeg_location': opts
.ffmpeg_location
,
778 'hls_prefer_native': opts
.hls_prefer_native
,
779 'hls_use_mpegts': opts
.hls_use_mpegts
,
780 'hls_split_discontinuity': opts
.hls_split_discontinuity
,
781 'external_downloader_args': opts
.external_downloader_args
,
782 'postprocessor_args': opts
.postprocessor_args
,
783 'cn_verification_proxy': opts
.cn_verification_proxy
,
784 'geo_verification_proxy': opts
.geo_verification_proxy
,
785 'geo_bypass': opts
.geo_bypass
,
786 'geo_bypass_country': opts
.geo_bypass_country
,
787 'geo_bypass_ip_block': opts
.geo_bypass_ip_block
,
788 '_warnings': warnings
,
789 '_deprecation_warnings': deprecation_warnings
,
790 'compat_opts': compat_opts
,
793 with YoutubeDL(ydl_opts
) as ydl
:
794 actual_use
= all_urls
or opts
.load_info_filename
802 # If updater returns True, exit. Required for windows
805 sys
.exit('ERROR: The program must exit for the update to complete')
810 if opts
.update_self
or opts
.rm_cachedir
:
813 ydl
.warn_if_short_id(sys
.argv
[1:] if argv
is None else argv
)
815 'You must provide at least one URL.\n'
816 'Type yt-dlp --help to see a list of all options.')
819 if opts
.load_info_filename
is not None:
820 retcode
= ydl
.download_with_info_file(expand_path(opts
.load_info_filename
))
822 retcode
= ydl
.download(all_urls
)
823 except DownloadCancelled
:
824 ydl
.to_screen('Aborting remaining downloads')
833 except DownloadError
:
835 except SameFileError
as e
:
836 sys
.exit(f
'ERROR: {e}')
837 except KeyboardInterrupt:
838 sys
.exit('\nERROR: Interrupted by user')
839 except BrokenPipeError
as e
:
840 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
841 devnull
= os
.open(os
.devnull
, os
.O_WRONLY
)
842 os
.dup2(devnull
, sys
.stdout
.fileno())
843 sys
.exit(f
'\nERROR: {e}')
846 __all__
= ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors']