Merge pull request #730 by @JohnyMoSwag
[youtube-dl] / youtube_dl / __init__.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from __future__ import with_statement
5 from __future__ import absolute_import
6
7 __authors__  = (
8     'Ricardo Garcia Gonzalez',
9     'Danny Colligan',
10     'Benjamin Johnson',
11     'Vasyl\' Vavrychuk',
12     'Witold Baryluk',
13     'Paweł Paprota',
14     'Gergely Imreh',
15     'Rogério Brito',
16     'Philipp Hagemeister',
17     'Sören Schulze',
18     'Kevin Ngo',
19     'Ori Avtalion',
20     'shizeeg',
21     'Filippo Valsorda',
22     'Christian Albrecht',
23     'Dave Vasilevsky',
24     'Jaime Marquínez Ferrándiz',
25     'Jeff Crouse',
26     'Osama Khalid',
27     )
28
29 __license__ = 'Public Domain'
30
31 import getpass
32 import optparse
33 import os
34 import re
35 import shlex
36 import socket
37 import subprocess
38 import sys
39 import warnings
40 import platform
41
42 from .utils import *
43 from .update import update_self
44 from .version import __version__
45 from .FileDownloader import *
46 from .InfoExtractors import gen_extractors
47 from .PostProcessor import *
48
49 def parseOpts():
50     def _readOptions(filename_bytes):
51         try:
52             optionf = open(filename_bytes)
53         except IOError:
54             return [] # silently skip if file is not present
55         try:
56             res = []
57             for l in optionf:
58                 res += shlex.split(l, comments=True)
59         finally:
60             optionf.close()
61         return res
62
63     def _format_option_string(option):
64         ''' ('-o', '--option') -> -o, --format METAVAR'''
65
66         opts = []
67
68         if option._short_opts:
69             opts.append(option._short_opts[0])
70         if option._long_opts:
71             opts.append(option._long_opts[0])
72         if len(opts) > 1:
73             opts.insert(1, ', ')
74
75         if option.takes_value(): opts.append(' %s' % option.metavar)
76
77         return "".join(opts)
78
79     def _find_term_columns():
80         columns = os.environ.get('COLUMNS', None)
81         if columns:
82             return int(columns)
83
84         try:
85             sp = subprocess.Popen(['stty', 'size'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
86             out,err = sp.communicate()
87             return int(out.split()[1])
88         except:
89             pass
90         return None
91
92     max_width = 80
93     max_help_position = 80
94
95     # No need to wrap help messages if we're on a wide console
96     columns = _find_term_columns()
97     if columns: max_width = columns
98
99     fmt = optparse.IndentedHelpFormatter(width=max_width, max_help_position=max_help_position)
100     fmt.format_option_strings = _format_option_string
101
102     kw = {
103         'version'   : __version__,
104         'formatter' : fmt,
105         'usage' : '%prog [options] url [url...]',
106         'conflict_handler' : 'resolve',
107     }
108
109     parser = optparse.OptionParser(**kw)
110
111     # option groups
112     general        = optparse.OptionGroup(parser, 'General Options')
113     selection      = optparse.OptionGroup(parser, 'Video Selection')
114     authentication = optparse.OptionGroup(parser, 'Authentication Options')
115     video_format   = optparse.OptionGroup(parser, 'Video Format Options')
116     postproc       = optparse.OptionGroup(parser, 'Post-processing Options')
117     filesystem     = optparse.OptionGroup(parser, 'Filesystem Options')
118     verbosity      = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
119
120     general.add_option('-h', '--help',
121             action='help', help='print this help text and exit')
122     general.add_option('-v', '--version',
123             action='version', help='print program version and exit')
124     general.add_option('-U', '--update',
125             action='store_true', dest='update_self', help='update this program to latest version')
126     general.add_option('-i', '--ignore-errors',
127             action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
128     general.add_option('-r', '--rate-limit',
129             dest='ratelimit', metavar='LIMIT', help='maximum download rate (e.g. 50k or 44.6m)')
130     general.add_option('-R', '--retries',
131             dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
132     general.add_option('--buffer-size',
133             dest='buffersize', metavar='SIZE', help='size of download buffer (e.g. 1024 or 16k) (default is %default)', default="1024")
134     general.add_option('--no-resize-buffer',
135             action='store_true', dest='noresizebuffer',
136             help='do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.', default=False)
137     general.add_option('--dump-user-agent',
138             action='store_true', dest='dump_user_agent',
139             help='display the current browser identification', default=False)
140     general.add_option('--user-agent',
141             dest='user_agent', help='specify a custom user agent', metavar='UA')
142     general.add_option('--list-extractors',
143             action='store_true', dest='list_extractors',
144             help='List all supported extractors and the URLs they would handle', default=False)
145     general.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
146
147     selection.add_option('--playlist-start',
148             dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is %default)', default=1)
149     selection.add_option('--playlist-end',
150             dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
151     selection.add_option('--match-title', dest='matchtitle', metavar='REGEX',help='download only matching titles (regex or caseless sub-string)')
152     selection.add_option('--reject-title', dest='rejecttitle', metavar='REGEX',help='skip download for matching titles (regex or caseless sub-string)')
153     selection.add_option('--max-downloads', metavar='NUMBER', dest='max_downloads', help='Abort after downloading NUMBER files', default=None)
154     selection.add_option('--min-filesize', metavar='SIZE', dest='min_filesize', help="Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)", default=None)
155     selection.add_option('--max-filesize', metavar='SIZE', dest='max_filesize', help="Do not download any videos larger than SIZE (e.g. 50k or 44.6m)", default=None)
156
157
158     authentication.add_option('-u', '--username',
159             dest='username', metavar='USERNAME', help='account username')
160     authentication.add_option('-p', '--password',
161             dest='password', metavar='PASSWORD', help='account password')
162     authentication.add_option('-n', '--netrc',
163             action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
164
165
166     video_format.add_option('-f', '--format',
167             action='store', dest='format', metavar='FORMAT', help='video format code')
168     video_format.add_option('--all-formats',
169             action='store_const', dest='format', help='download all available video formats', const='all')
170     video_format.add_option('--prefer-free-formats',
171             action='store_true', dest='prefer_free_formats', default=False, help='prefer free video formats unless a specific one is requested')
172     video_format.add_option('--max-quality',
173             action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
174     video_format.add_option('-F', '--list-formats',
175             action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
176     video_format.add_option('--write-sub', '--write-srt',
177             action='store_true', dest='writesubtitles',
178             help='write subtitle file (currently youtube only)', default=False)
179     video_format.add_option('--only-sub',
180             action='store_true', dest='onlysubtitles',
181             help='downloads only the subtitles (no video)', default=False)
182     video_format.add_option('--all-subs',
183             action='store_true', dest='allsubtitles',
184             help='downloads all the available subtitles of the video (currently youtube only)', default=False)
185     video_format.add_option('--list-subs',
186             action='store_true', dest='listsubtitles',
187             help='lists all available subtitles for the video (currently youtube only)', default=False)
188     video_format.add_option('--sub-format',
189             action='store', dest='subtitlesformat', metavar='LANG',
190             help='subtitle format [srt/sbv] (default=srt) (currently youtube only)', default='srt')
191     video_format.add_option('--sub-lang', '--srt-lang',
192             action='store', dest='subtitleslang', metavar='LANG',
193             help='language of the subtitles to download (optional) use IETF language tags like \'en\'')
194
195     verbosity.add_option('-q', '--quiet',
196             action='store_true', dest='quiet', help='activates quiet mode', default=False)
197     verbosity.add_option('-s', '--simulate',
198             action='store_true', dest='simulate', help='do not download the video and do not write anything to disk', default=False)
199     verbosity.add_option('--skip-download',
200             action='store_true', dest='skip_download', help='do not download the video', default=False)
201     verbosity.add_option('-g', '--get-url',
202             action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
203     verbosity.add_option('-e', '--get-title',
204             action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
205     verbosity.add_option('--get-thumbnail',
206             action='store_true', dest='getthumbnail',
207             help='simulate, quiet but print thumbnail URL', default=False)
208     verbosity.add_option('--get-description',
209             action='store_true', dest='getdescription',
210             help='simulate, quiet but print video description', default=False)
211     verbosity.add_option('--get-filename',
212             action='store_true', dest='getfilename',
213             help='simulate, quiet but print output filename', default=False)
214     verbosity.add_option('--get-format',
215             action='store_true', dest='getformat',
216             help='simulate, quiet but print output format', default=False)
217     verbosity.add_option('--newline',
218             action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
219     verbosity.add_option('--no-progress',
220             action='store_true', dest='noprogress', help='do not print progress bar', default=False)
221     verbosity.add_option('--console-title',
222             action='store_true', dest='consoletitle',
223             help='display progress in console titlebar', default=False)
224     verbosity.add_option('-v', '--verbose',
225             action='store_true', dest='verbose', help='print various debugging information', default=False)
226
227     filesystem.add_option('-t', '--title',
228             action='store_true', dest='usetitle', help='use title in file name', default=False)
229     filesystem.add_option('--id',
230             action='store_true', dest='useid', help='use video ID in file name', default=False)
231     filesystem.add_option('-l', '--literal',
232             action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
233     filesystem.add_option('-A', '--auto-number',
234             action='store_true', dest='autonumber',
235             help='number downloaded files starting from 00000', default=False)
236     filesystem.add_option('-o', '--output',
237             dest='outtmpl', metavar='TEMPLATE', help='output filename template. Use %(title)s to get the title, %(uploader)s for the uploader name, %(uploader_id)s for the uploader nickname if different, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(upload_date)s for the upload date (YYYYMMDD), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id and %% for a literal percent. Use - to output to stdout. Can also be used to download to a different directory, for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .')
238     filesystem.add_option('--restrict-filenames',
239             action='store_true', dest='restrictfilenames',
240             help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
241     filesystem.add_option('-a', '--batch-file',
242             dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
243     filesystem.add_option('-w', '--no-overwrites',
244             action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
245     filesystem.add_option('-c', '--continue',
246             action='store_true', dest='continue_dl', help='resume partially downloaded files', default=True)
247     filesystem.add_option('--no-continue',
248             action='store_false', dest='continue_dl',
249             help='do not resume partially downloaded files (restart from beginning)')
250     filesystem.add_option('--cookies',
251             dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
252     filesystem.add_option('--no-part',
253             action='store_true', dest='nopart', help='do not use .part files', default=False)
254     filesystem.add_option('--no-mtime',
255             action='store_false', dest='updatetime',
256             help='do not use the Last-modified header to set the file modification time', default=True)
257     filesystem.add_option('--write-description',
258             action='store_true', dest='writedescription',
259             help='write video description to a .description file', default=False)
260     filesystem.add_option('--write-info-json',
261             action='store_true', dest='writeinfojson',
262             help='write video metadata to a .info.json file', default=False)
263
264
265     postproc.add_option('-x', '--extract-audio', action='store_true', dest='extractaudio', default=False,
266             help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
267     postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
268             help='"best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; best by default')
269     postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
270             help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
271     postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
272             help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)')
273     postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
274             help='keeps the video file on disk after the post-processing; the video is erased by default')
275     postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
276             help='do not overwrite post-processed files; the post-processed files are overwritten by default')
277
278
279     parser.add_option_group(general)
280     parser.add_option_group(selection)
281     parser.add_option_group(filesystem)
282     parser.add_option_group(verbosity)
283     parser.add_option_group(video_format)
284     parser.add_option_group(authentication)
285     parser.add_option_group(postproc)
286
287     xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
288     if xdg_config_home:
289         userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
290     else:
291         userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
292     systemConf = _readOptions('/etc/youtube-dl.conf')
293     userConf = _readOptions(userConfFile)
294     commandLineConf = sys.argv[1:]
295     argv = systemConf + userConf + commandLineConf
296     opts, args = parser.parse_args(argv)
297
298     if opts.verbose:
299         print(u'[debug] System config: ' + repr(systemConf))
300         print(u'[debug] User config: ' + repr(userConf))
301         print(u'[debug] Command-line args: ' + repr(commandLineConf))
302
303     return parser, opts, args
304
305 def _real_main():
306     parser, opts, args = parseOpts()
307
308     # Open appropriate CookieJar
309     if opts.cookiefile is None:
310         jar = compat_cookiejar.CookieJar()
311     else:
312         try:
313             jar = compat_cookiejar.MozillaCookieJar(opts.cookiefile)
314             if os.access(opts.cookiefile, os.R_OK):
315                 jar.load()
316         except (IOError, OSError) as err:
317             if opts.verbose:
318                 traceback.print_exc()
319             sys.stderr.write(u'ERROR: unable to open cookie file\n')
320             sys.exit(101)
321     # Set user agent
322     if opts.user_agent is not None:
323         std_headers['User-Agent'] = opts.user_agent
324
325     # Dump user agent
326     if opts.dump_user_agent:
327         print(std_headers['User-Agent'])
328         sys.exit(0)
329
330     # Batch file verification
331     batchurls = []
332     if opts.batchfile is not None:
333         try:
334             if opts.batchfile == '-':
335                 batchfd = sys.stdin
336             else:
337                 batchfd = open(opts.batchfile, 'r')
338             batchurls = batchfd.readlines()
339             batchurls = [x.strip() for x in batchurls]
340             batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)]
341         except IOError:
342             sys.exit(u'ERROR: batch file could not be read')
343     all_urls = batchurls + args
344     all_urls = [url.strip() for url in all_urls]
345
346     # General configuration
347     cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
348     proxy_handler = compat_urllib_request.ProxyHandler()
349     opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
350     compat_urllib_request.install_opener(opener)
351     socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
352
353     extractors = gen_extractors()
354
355     if opts.list_extractors:
356         for ie in extractors:
357             print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
358             matchedUrls = [url for url in all_urls if ie.suitable(url)]
359             all_urls = [url for url in all_urls if url not in matchedUrls]
360             for mu in matchedUrls:
361                 print(u'  ' + mu)
362         sys.exit(0)
363
364     # Conflicting, missing and erroneous options
365     if opts.usenetrc and (opts.username is not None or opts.password is not None):
366         parser.error(u'using .netrc conflicts with giving username/password')
367     if opts.password is not None and opts.username is None:
368         parser.error(u'account username missing')
369     if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
370         parser.error(u'using output template conflicts with using title, video ID or auto number')
371     if opts.usetitle and opts.useid:
372         parser.error(u'using title conflicts with using video ID')
373     if opts.username is not None and opts.password is None:
374         opts.password = getpass.getpass(u'Type account password and press return:')
375     if opts.ratelimit is not None:
376         numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
377         if numeric_limit is None:
378             parser.error(u'invalid rate limit specified')
379         opts.ratelimit = numeric_limit
380     if opts.min_filesize is not None:
381         numeric_limit = FileDownloader.parse_bytes(opts.min_filesize)
382         if numeric_limit is None:
383             parser.error(u'invalid min_filesize specified')
384         opts.min_filesize = numeric_limit
385     if opts.max_filesize is not None:
386         numeric_limit = FileDownloader.parse_bytes(opts.max_filesize)
387         if numeric_limit is None:
388             parser.error(u'invalid max_filesize specified')
389         opts.max_filesize = numeric_limit
390     if opts.retries is not None:
391         try:
392             opts.retries = int(opts.retries)
393         except (TypeError, ValueError) as err:
394             parser.error(u'invalid retry count specified')
395     if opts.buffersize is not None:
396         numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
397         if numeric_buffersize is None:
398             parser.error(u'invalid buffer size specified')
399         opts.buffersize = numeric_buffersize
400     try:
401         opts.playliststart = int(opts.playliststart)
402         if opts.playliststart <= 0:
403             raise ValueError(u'Playlist start must be positive')
404     except (TypeError, ValueError) as err:
405         parser.error(u'invalid playlist start number specified')
406     try:
407         opts.playlistend = int(opts.playlistend)
408         if opts.playlistend != -1 and (opts.playlistend <= 0 or opts.playlistend < opts.playliststart):
409             raise ValueError(u'Playlist end must be greater than playlist start')
410     except (TypeError, ValueError) as err:
411         parser.error(u'invalid playlist end number specified')
412     if opts.extractaudio:
413         if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
414             parser.error(u'invalid audio format specified')
415     if opts.audioquality:
416         opts.audioquality = opts.audioquality.strip('k').strip('K')
417         if not opts.audioquality.isdigit():
418             parser.error(u'invalid audio quality specified')
419     if opts.recodevideo is not None:
420         if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
421             parser.error(u'invalid video recode format specified')
422
423     if sys.version_info < (3,):
424         # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
425         if opts.outtmpl is not None:
426             opts.outtmpl = opts.outtmpl.decode(preferredencoding())
427     outtmpl =((opts.outtmpl is not None and opts.outtmpl)
428             or (opts.format == '-1' and opts.usetitle and u'%(title)s-%(id)s-%(format)s.%(ext)s')
429             or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
430             or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
431             or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
432             or (opts.useid and u'%(id)s.%(ext)s')
433             or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
434             or u'%(id)s.%(ext)s')
435
436     # File downloader
437     fd = FileDownloader({
438         'usenetrc': opts.usenetrc,
439         'username': opts.username,
440         'password': opts.password,
441         'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
442         'forceurl': opts.geturl,
443         'forcetitle': opts.gettitle,
444         'forcethumbnail': opts.getthumbnail,
445         'forcedescription': opts.getdescription,
446         'forcefilename': opts.getfilename,
447         'forceformat': opts.getformat,
448         'simulate': opts.simulate,
449         'skip_download': (opts.skip_download or opts.simulate or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
450         'format': opts.format,
451         'format_limit': opts.format_limit,
452         'listformats': opts.listformats,
453         'outtmpl': outtmpl,
454         'restrictfilenames': opts.restrictfilenames,
455         'ignoreerrors': opts.ignoreerrors,
456         'ratelimit': opts.ratelimit,
457         'nooverwrites': opts.nooverwrites,
458         'retries': opts.retries,
459         'buffersize': opts.buffersize,
460         'noresizebuffer': opts.noresizebuffer,
461         'continuedl': opts.continue_dl,
462         'noprogress': opts.noprogress,
463         'progress_with_newline': opts.progress_with_newline,
464         'playliststart': opts.playliststart,
465         'playlistend': opts.playlistend,
466         'logtostderr': opts.outtmpl == '-',
467         'consoletitle': opts.consoletitle,
468         'nopart': opts.nopart,
469         'updatetime': opts.updatetime,
470         'writedescription': opts.writedescription,
471         'writeinfojson': opts.writeinfojson,
472         'writesubtitles': opts.writesubtitles,
473         'onlysubtitles': opts.onlysubtitles,
474         'allsubtitles': opts.allsubtitles,
475         'listsubtitles': opts.listsubtitles,
476         'subtitlesformat': opts.subtitlesformat,
477         'subtitleslang': opts.subtitleslang,
478         'matchtitle': decodeOption(opts.matchtitle),
479         'rejecttitle': decodeOption(opts.rejecttitle),
480         'max_downloads': opts.max_downloads,
481         'prefer_free_formats': opts.prefer_free_formats,
482         'verbose': opts.verbose,
483         'test': opts.test,
484         'keepvideo': opts.keepvideo,
485         'min_filesize': opts.min_filesize,
486         'max_filesize': opts.max_filesize
487         })
488
489     if opts.verbose:
490         fd.to_screen(u'[debug] youtube-dl version ' + __version__)
491         try:
492             sp = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
493                                   cwd=os.path.dirname(os.path.abspath(__file__)))
494             out, err = sp.communicate()
495             out = out.decode().strip()
496             if re.match('[0-9a-f]+', out):
497                 fd.to_screen(u'[debug] Git HEAD: ' + out)
498         except:
499             pass
500         fd.to_screen(u'[debug] Python version %s - %s' %(platform.python_version(), platform.platform()))
501         fd.to_screen(u'[debug] Proxy map: ' + str(proxy_handler.proxies))
502
503     for extractor in extractors:
504         fd.add_info_extractor(extractor)
505
506     # PostProcessors
507     if opts.extractaudio:
508         fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
509     if opts.recodevideo:
510         fd.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
511
512     # Update version
513     if opts.update_self:
514         update_self(fd.to_screen, opts.verbose, sys.argv[0])
515
516     # Maybe do nothing
517     if len(all_urls) < 1:
518         if not opts.update_self:
519             parser.error(u'you must provide at least one URL')
520         else:
521             sys.exit()
522
523     try:
524         retcode = fd.download(all_urls)
525     except MaxDownloadsReached:
526         fd.to_screen(u'--max-download limit reached, aborting.')
527         retcode = 101
528
529     # Dump cookie jar if requested
530     if opts.cookiefile is not None:
531         try:
532             jar.save()
533         except (IOError, OSError) as err:
534             sys.exit(u'ERROR: unable to save cookie jar')
535
536     sys.exit(retcode)
537
538 def main():
539     try:
540         _real_main()
541     except DownloadError:
542         sys.exit(1)
543     except SameFileError:
544         sys.exit(u'ERROR: fixed output name but more than one file to download')
545     except KeyboardInterrupt:
546         sys.exit(u'\nERROR: Interrupted by user')