]> jfr.im git - yt-dlp.git/blame - youtube_dl/__init__.py
[options] Move all options to their own file
[yt-dlp.git] / youtube_dl / __init__.py
CommitLineData
235b3ba4
PH
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
3906e6ce 4__authors__ = (
59ae15a5
PH
5 'Ricardo Garcia Gonzalez',
6 'Danny Colligan',
7 'Benjamin Johnson',
8 'Vasyl\' Vavrychuk',
9 'Witold Baryluk',
10 'Paweł Paprota',
11 'Gergely Imreh',
12 'Rogério Brito',
13 'Philipp Hagemeister',
14 'Sören Schulze',
15 'Kevin Ngo',
16 'Ori Avtalion',
17 'shizeeg',
18 'Filippo Valsorda',
19 'Christian Albrecht',
88f6c78b 20 'Dave Vasilevsky',
2069acc6 21 'Jaime Marquínez Ferrándiz',
fffec3b9 22 'Jeff Crouse',
6aabe820 23 'Osama Khalid',
e8600d69 24 'Michael Walter',
95464f14 25 'M. Yasoob Ullah Khalid',
0ae456f0 26 'Julien Fraichard',
be74864a 27 'Johny Mo Swag',
df725153 28 'Axel Noack',
ba7a1de0 29 'Albert Kim',
4a55479f 30 'Pierre Rudloff',
085bea45 31 'Huarong Huo',
ac4f319b 32 'Ismael Mejía',
2dad310e 33 'Steffan \'Ruirize\' James',
a623df4c 34 'Andras Elso',
b5bdc269 35 'Jelle van der Waa',
d3793638 36 'Marcin Cieślak',
0a120f74 37 'Anton Larionov',
38b2db6a 38 'Takuya Tsuchida',
87968574 39 'Sergey M.',
b83be81d 40 'Michael Orlitzky',
e63fc1be 41 'Chris Gahan',
a7732b67 42 'Saimadhav Heblikar',
2a893862 43 'Mike Col',
6d784e87 44 'Oleg Prutz',
0cea52cc 45 'pulpe',
845d14d3 46 'Andreas Schmitz',
cbffec0c 47 'Michael Kaiser',
96d16370 48 'Niklas Laxström',
f38da667 49 'David Triendl',
a339d7ba 50 'Anthony Weems',
dc3727b6 51 'David Wagner',
2fcec131 52 'Juan C. Olivares',
855e2750 53 'Mattias Harrysson',
2cc0082d 54 'phaer',
610e47c8 55 'Sainyam Kapoor',
bbe99d26 56 'Nicolas Évrard',
ccdd34ed 57 'Jason Normore',
25dfe0eb 58 'Hoje Lee',
c2ef2923 59 'Adam Thalhammer',
d30d2815 60 'Georg Jähnig',
9cc977f1 61 'Ralf Haring',
ba4133c9 62 'Koki Takahashi',
3d55f280 63 'Ariset Llerena',
cdc22cb8 64 'Adam Malcontenti-Wilson',
1df0ae21 65 'Tobias Bell',
0d90e0f0 66 'Naglis Jonaitis',
3b09757b 67 'Charles Chen',
5e95cb27 68 'Hassaan Ali',
e48a2c64 69 'Dobrosław Żybort',
4665664c 70 'David Fabijan',
56ca04f6 71 'Sebastian Haas',
3f338cd6 72 'Alexander Kirk',
deda8ac3 73 'Erik Johnson',
00558d94 74 'Keith Beckman',
36b0079f 75 'Ole Ernst',
a7cacbca 76 'Aaron McDaniel (mcd1992)',
140d8d77 77 'Magnus Kolstad',
ba7a1de0 78)
235b3ba4
PH
79
80__license__ = 'Public Domain'
235b3ba4 81
0d94f247 82import codecs
8f563f32 83import io
235b3ba4 84import os
0f818663 85import random
235b3ba4 86import sys
235b3ba4 87
c496ca96 88
a4fd0415 89from .utils import (
e68301af 90 compat_getpass,
a4fd0415 91 compat_print,
a4fd0415 92 DateRange,
acd69589 93 DEFAULT_OUTTMPL,
a4fd0415 94 decodeOption,
a4fd0415 95 DownloadError,
a4fd0415 96 MaxDownloadsReached,
34a741a8 97 parseOpts,
a4fd0415 98 preferredencoding,
62e609ab 99 read_batch_urls,
a4fd0415 100 SameFileError,
e3946f98 101 setproctitle,
a4fd0415
PH
102 std_headers,
103 write_string,
a4fd0415 104)
d5ed35b6 105from .update import update_self
92a86f4c 106from .downloader import (
a4fd0415
PH
107 FileDownloader,
108)
0824c28c 109from .extractor import gen_extractors
8222d8de 110from .YoutubeDL import YoutubeDL
56327689 111from .postprocessor import (
0c14e2fb 112 AtomicParsleyPP,
149254d0 113 FFmpegAudioFixPP,
a4fd0415
PH
114 FFmpegMetadataPP,
115 FFmpegVideoConvertor,
116 FFmpegExtractAudioPP,
117 FFmpegEmbedSubtitlePP,
e63fc1be 118 XAttrMetadataPP,
a2360a4c 119 ExecAfterDownloadPP,
a4fd0415
PH
120)
121
235b3ba4 122
b8ad4f02 123def _real_main(argv=None):
0d94f247
PH
124 # Compatibility fixes for Windows
125 if sys.platform == 'win32':
126 # https://github.com/rg3/youtube-dl/issues/820
127 codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)
128
e3946f98
PH
129 setproctitle(u'youtube-dl')
130
b8ad4f02 131 parser, opts, args = parseOpts(argv)
59ae15a5 132
59ae15a5
PH
133 # Set user agent
134 if opts.user_agent is not None:
135 std_headers['User-Agent'] = opts.user_agent
1865ed31 136
28535652
BH
137 # Set referer
138 if opts.referer is not None:
139 std_headers['Referer'] = opts.referer
59ae15a5 140
410afb20
AA
141 # Custom HTTP headers
142 if opts.headers is not None:
143 for h in opts.headers:
144 if h.find(':', 1) < 0:
145 parser.error(u'wrong header formatting, it should be key:value, not "%s"'%h)
146 key, value = h.split(':', 2)
147 if opts.verbose:
148 write_string(u'[debug] Adding header from command line option %s:%s\n'%(key, value))
149 std_headers[key] = value
150
59ae15a5
PH
151 # Dump user agent
152 if opts.dump_user_agent:
93eb15c5 153 compat_print(std_headers['User-Agent'])
59ae15a5
PH
154 sys.exit(0)
155
156 # Batch file verification
62e609ab 157 batch_urls = []
59ae15a5
PH
158 if opts.batchfile is not None:
159 try:
160 if opts.batchfile == '-':
161 batchfd = sys.stdin
162 else:
62e609ab
PH
163 batchfd = io.open(opts.batchfile, 'r', encoding='utf-8', errors='ignore')
164 batch_urls = read_batch_urls(batchfd)
05afc96b 165 if opts.verbose:
62e609ab 166 write_string(u'[debug] Batch file urls: ' + repr(batch_urls) + u'\n')
59ae15a5
PH
167 except IOError:
168 sys.exit(u'ERROR: batch file could not be read')
62e609ab 169 all_urls = batch_urls + args
59ae15a5 170 all_urls = [url.strip() for url in all_urls]
c774b3c6 171 _enc = preferredencoding()
41292a38 172 all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
59ae15a5 173
59ae15a5
PH
174 extractors = gen_extractors()
175
176 if opts.list_extractors:
7dba9cd0 177 for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()):
93eb15c5 178 compat_print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
1a2c3c0f 179 matchedUrls = [url for url in all_urls if ie.suitable(url)]
59ae15a5 180 for mu in matchedUrls:
93eb15c5 181 compat_print(u' ' + mu)
59ae15a5 182 sys.exit(0)
0f818663
PH
183 if opts.list_extractor_descriptions:
184 for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()):
185 if not ie._WORKING:
186 continue
187 desc = getattr(ie, 'IE_DESC', ie.IE_NAME)
15870e90
PH
188 if desc is False:
189 continue
0f818663 190 if hasattr(ie, 'SEARCH_KEY'):
53eb2176 191 _SEARCHES = (u'cute kittens', u'slithering pythons', u'falling cat', u'angry poodle', u'purple fish', u'running tortoise', u'sleeping bunny')
0f818663
PH
192 _COUNTS = (u'', u'5', u'10', u'all')
193 desc += u' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
194 compat_print(desc)
195 sys.exit(0)
196
59ae15a5
PH
197
198 # Conflicting, missing and erroneous options
199 if opts.usenetrc and (opts.username is not None or opts.password is not None):
200 parser.error(u'using .netrc conflicts with giving username/password')
201 if opts.password is not None and opts.username is None:
67d28bff 202 parser.error(u'account username missing\n')
59ae15a5
PH
203 if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
204 parser.error(u'using output template conflicts with using title, video ID or auto number')
205 if opts.usetitle and opts.useid:
206 parser.error(u'using title conflicts with using video ID')
207 if opts.username is not None and opts.password is None:
e68301af 208 opts.password = compat_getpass(u'Type account password and press [Return]: ')
59ae15a5
PH
209 if opts.ratelimit is not None:
210 numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
211 if numeric_limit is None:
212 parser.error(u'invalid rate limit specified')
213 opts.ratelimit = numeric_limit
9e982f9e
JC
214 if opts.min_filesize is not None:
215 numeric_limit = FileDownloader.parse_bytes(opts.min_filesize)
216 if numeric_limit is None:
217 parser.error(u'invalid min_filesize specified')
218 opts.min_filesize = numeric_limit
219 if opts.max_filesize is not None:
220 numeric_limit = FileDownloader.parse_bytes(opts.max_filesize)
221 if numeric_limit is None:
222 parser.error(u'invalid max_filesize specified')
223 opts.max_filesize = numeric_limit
59ae15a5
PH
224 if opts.retries is not None:
225 try:
226 opts.retries = int(opts.retries)
dca08720 227 except (TypeError, ValueError):
59ae15a5
PH
228 parser.error(u'invalid retry count specified')
229 if opts.buffersize is not None:
230 numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
231 if numeric_buffersize is None:
232 parser.error(u'invalid buffer size specified')
233 opts.buffersize = numeric_buffersize
a19fd00c
PH
234 if opts.playliststart <= 0:
235 raise ValueError(u'Playlist start must be positive')
236 if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart:
237 raise ValueError(u'Playlist end must be greater than playlist start')
59ae15a5 238 if opts.extractaudio:
510e6f6d 239 if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
59ae15a5
PH
240 parser.error(u'invalid audio format specified')
241 if opts.audioquality:
242 opts.audioquality = opts.audioquality.strip('k').strip('K')
243 if not opts.audioquality.isdigit():
244 parser.error(u'invalid audio quality specified')
7851b379 245 if opts.recodevideo is not None:
b7d73595 246 if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']:
7851b379 247 parser.error(u'invalid video recode format specified')
bd558525
JMF
248 if opts.date is not None:
249 date = DateRange.day(opts.date)
250 else:
251 date = DateRange(opts.dateafter, opts.datebefore)
1f7ccb90 252 if opts.default_search not in ('auto', 'auto_warning', 'error', 'fixup_error', None) and ':' not in opts.default_search:
04b4d394 253 parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
59ae15a5 254
de3ef3ed
PH
255 # Do not download videos when there are audio-only formats
256 if opts.extractaudio and not opts.keepvideo and opts.format is None:
257 opts.format = 'bestaudio/best'
258
0b7f3118
JMF
259 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
260 # this was the old behaviour if only --all-sub was given.
261 if opts.allsubtitles and (opts.writeautomaticsub == False):
262 opts.writesubtitles = True
263
5cb9c312
PH
264 if sys.version_info < (3,):
265 # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
0be41ec2
PH
266 if opts.outtmpl is not None:
267 opts.outtmpl = opts.outtmpl.decode(preferredencoding())
5cb9c312
PH
268 outtmpl =((opts.outtmpl is not None and opts.outtmpl)
269 or (opts.format == '-1' and opts.usetitle and u'%(title)s-%(id)s-%(format)s.%(ext)s')
270 or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
271 or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
272 or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
273 or (opts.useid and u'%(id)s.%(ext)s')
274 or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
acd69589 275 or DEFAULT_OUTTMPL)
dca02c80 276 if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
b61067fa 277 parser.error(u'Cannot download a video and extract audio into the same'
dca02c80
JMF
278 u' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
279 u' template'.format(outtmpl))
29c7a63d 280
525ef922 281 any_printing = 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
17093b83 282 download_archive_fn = os.path.expanduser(opts.download_archive) if opts.download_archive is not None else opts.download_archive
525ef922 283
bdde425c 284 ydl_opts = {
59ae15a5
PH
285 'usenetrc': opts.usenetrc,
286 'username': opts.username,
287 'password': opts.password,
83317f69 288 'twofactor': opts.twofactor,
c6c19746 289 'videopassword': opts.videopassword,
525ef922 290 'quiet': (opts.quiet or any_printing),
ad8915b7 291 'no_warnings': opts.no_warnings,
59ae15a5
PH
292 'forceurl': opts.geturl,
293 'forcetitle': opts.gettitle,
1a2adf3f 294 'forceid': opts.getid,
59ae15a5
PH
295 'forcethumbnail': opts.getthumbnail,
296 'forcedescription': opts.getdescription,
525ef922 297 'forceduration': opts.getduration,
59ae15a5
PH
298 'forcefilename': opts.getfilename,
299 'forceformat': opts.getformat,
9d153818 300 'forcejson': opts.dumpjson,
59ae15a5 301 'simulate': opts.simulate,
525ef922 302 'skip_download': (opts.skip_download or opts.simulate or any_printing),
59ae15a5
PH
303 'format': opts.format,
304 'format_limit': opts.format_limit,
305 'listformats': opts.listformats,
5cb9c312 306 'outtmpl': outtmpl,
213c31ae 307 'autonumber_size': opts.autonumber_size,
59ae15a5
PH
308 'restrictfilenames': opts.restrictfilenames,
309 'ignoreerrors': opts.ignoreerrors,
310 'ratelimit': opts.ratelimit,
311 'nooverwrites': opts.nooverwrites,
312 'retries': opts.retries,
313 'buffersize': opts.buffersize,
314 'noresizebuffer': opts.noresizebuffer,
315 'continuedl': opts.continue_dl,
316 'noprogress': opts.noprogress,
5717d91a 317 'progress_with_newline': opts.progress_with_newline,
59ae15a5
PH
318 'playliststart': opts.playliststart,
319 'playlistend': opts.playlistend,
47192f92 320 'noplaylist': opts.noplaylist,
59ae15a5
PH
321 'logtostderr': opts.outtmpl == '-',
322 'consoletitle': opts.consoletitle,
323 'nopart': opts.nopart,
324 'updatetime': opts.updatetime,
325 'writedescription': opts.writedescription,
1fb07d10 326 'writeannotations': opts.writeannotations,
59ae15a5 327 'writeinfojson': opts.writeinfojson,
11d9224e 328 'writethumbnail': opts.writethumbnail,
59ae15a5 329 'writesubtitles': opts.writesubtitles,
b004821f 330 'writeautomaticsub': opts.writeautomaticsub,
ae608b80 331 'allsubtitles': opts.allsubtitles,
2a4093ea 332 'listsubtitles': opts.listsubtitles,
9e62bc44 333 'subtitlesformat': opts.subtitlesformat,
d6e203b3 334 'subtitleslangs': opts.subtitleslangs,
8271226a
PH
335 'matchtitle': decodeOption(opts.matchtitle),
336 'rejecttitle': decodeOption(opts.rejecttitle),
59ae15a5
PH
337 'max_downloads': opts.max_downloads,
338 'prefer_free_formats': opts.prefer_free_formats,
339 'verbose': opts.verbose,
855703e5 340 'dump_intermediate_pages': opts.dump_intermediate_pages,
d41e6efc 341 'write_pages': opts.write_pages,
8d5d3a5d 342 'test': opts.test,
7851b379 343 'keepvideo': opts.keepvideo,
9e982f9e 344 'min_filesize': opts.min_filesize,
bd558525 345 'max_filesize': opts.max_filesize,
5fe18bdb
PH
346 'min_views': opts.min_views,
347 'max_views': opts.max_views,
11d9224e 348 'daterange': date,
7f747732 349 'cachedir': opts.cachedir,
f8061589 350 'youtube_print_sig_code': opts.youtube_print_sig_code,
8dbe9899 351 'age_limit': opts.age_limit,
17093b83 352 'download_archive': download_archive_fn,
dca08720
PH
353 'cookiefile': opts.cookiefile,
354 'nocheckcertificate': opts.no_check_certificate,
7e8c0af0 355 'prefer_insecure': opts.prefer_insecure,
c2e52508 356 'proxy': opts.proxy,
6ad14cab 357 'socket_timeout': opts.socket_timeout,
0783b09b 358 'bidi_workaround': opts.bidi_workaround,
a0ddb8a2 359 'debug_printtraffic': opts.debug_printtraffic,
76b1bd67 360 'prefer_ffmpeg': opts.prefer_ffmpeg,
7b0817e8 361 'include_ads': opts.include_ads,
04b4d394 362 'default_search': opts.default_search,
4919603f 363 'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
62fec3b2 364 'encoding': opts.encoding,
8d31fa3c 365 'exec_cmd': opts.exec_cmd,
bdde425c 366 }
59ae15a5 367
bdde425c 368 with YoutubeDL(ydl_opts) as ydl:
dca08720 369 ydl.print_debug_header()
bdde425c
PH
370 ydl.add_default_info_extractors()
371
372 # PostProcessors
373 # Add the metadata pp first, the other pps will copy it
374 if opts.addmetadata:
375 ydl.add_post_processor(FFmpegMetadataPP())
376 if opts.extractaudio:
377 ydl.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
378 if opts.recodevideo:
379 ydl.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
380 if opts.embedsubtitles:
381 ydl.add_post_processor(FFmpegEmbedSubtitlePP(subtitlesformat=opts.subtitlesformat))
e63fc1be 382 if opts.xattrs:
383 ydl.add_post_processor(XAttrMetadataPP())
0c14e2fb 384 if opts.embedthumbnail:
784763c5 385 if not opts.addmetadata:
386 ydl.add_post_processor(FFmpegAudioFixPP())
0c14e2fb 387 ydl.add_post_processor(AtomicParsleyPP())
bdde425c 388
a7cacbca 389
390 # Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
391 # So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
8d31fa3c
PH
392 if opts.exec_cmd:
393 ydl.add_post_processor(ExecAfterDownloadPP(
394 verboseOutput=opts.verbose, exec_cmd=opts.exec_cmd))
a7cacbca 395
bdde425c
PH
396 # Update version
397 if opts.update_self:
398 update_self(ydl.to_screen, opts.verbose)
399
052421ff
PH
400 # Remove cache dir
401 if opts.rm_cachedir:
a0e07d31 402 ydl.cache.remove()
052421ff 403
bdde425c 404 # Maybe do nothing
1dcc4c0c 405 if (len(all_urls) < 1) and (opts.load_info_filename is None):
052421ff 406 if not (opts.update_self or opts.rm_cachedir):
bdde425c
PH
407 parser.error(u'you must provide at least one URL')
408 else:
409 sys.exit()
59ae15a5 410
bdde425c 411 try:
1dcc4c0c
JMF
412 if opts.load_info_filename is not None:
413 retcode = ydl.download_with_info_file(opts.load_info_filename)
414 else:
415 retcode = ydl.download(all_urls)
bdde425c
PH
416 except MaxDownloadsReached:
417 ydl.to_screen(u'--max-download limit reached, aborting.')
418 retcode = 101
59ae15a5 419
59ae15a5 420 sys.exit(retcode)
235b3ba4 421
a27b9e8b 422
b8ad4f02 423def main(argv=None):
59ae15a5 424 try:
b8ad4f02 425 _real_main(argv)
59ae15a5
PH
426 except DownloadError:
427 sys.exit(1)
428 except SameFileError:
429 sys.exit(u'ERROR: fixed output name but more than one file to download')
430 except KeyboardInterrupt:
431 sys.exit(u'\nERROR: Interrupted by user')