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