Added new option '--sub-format' to choose the format of the subtitles to downloade...
[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='download rate limit (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',
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('--sub-format',
186             action='store', dest='subtitlesformat', metavar='LANG',
187             help='subtitle format [srt/sbv] (default=srt) (currently youtube only)', default='srt')
188     video_format.add_option('--sub-lang',
189             action='store', dest='subtitleslang', metavar='LANG',
190             help='language of the subtitles to download (optional) use IETF language tags like \'en\'')
191
192     verbosity.add_option('-q', '--quiet',
193             action='store_true', dest='quiet', help='activates quiet mode', default=False)
194     verbosity.add_option('-s', '--simulate',
195             action='store_true', dest='simulate', help='do not download the video and do not write anything to disk', default=False)
196     verbosity.add_option('--skip-download',
197             action='store_true', dest='skip_download', help='do not download the video', default=False)
198     verbosity.add_option('-g', '--get-url',
199             action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
200     verbosity.add_option('-e', '--get-title',
201             action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
202     verbosity.add_option('--get-thumbnail',
203             action='store_true', dest='getthumbnail',
204             help='simulate, quiet but print thumbnail URL', default=False)
205     verbosity.add_option('--get-description',
206             action='store_true', dest='getdescription',
207             help='simulate, quiet but print video description', default=False)
208     verbosity.add_option('--get-filename',
209             action='store_true', dest='getfilename',
210             help='simulate, quiet but print output filename', default=False)
211     verbosity.add_option('--get-format',
212             action='store_true', dest='getformat',
213             help='simulate, quiet but print output format', default=False)
214     verbosity.add_option('--newline',
215             action='store_true', dest='progress_with_newline', help='output progress bar as new lines', default=False)
216     verbosity.add_option('--no-progress',
217             action='store_true', dest='noprogress', help='do not print progress bar', default=False)
218     verbosity.add_option('--console-title',
219             action='store_true', dest='consoletitle',
220             help='display progress in console titlebar', default=False)
221     verbosity.add_option('-v', '--verbose',
222             action='store_true', dest='verbose', help='print various debugging information', default=False)
223
224     filesystem.add_option('-t', '--title',
225             action='store_true', dest='usetitle', help='use title in file name', default=False)
226     filesystem.add_option('--id',
227             action='store_true', dest='useid', help='use video ID in file name', default=False)
228     filesystem.add_option('-l', '--literal',
229             action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
230     filesystem.add_option('-A', '--auto-number',
231             action='store_true', dest='autonumber',
232             help='number downloaded files starting from 00000', default=False)
233     filesystem.add_option('-o', '--output',
234             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\' .')
235     filesystem.add_option('--restrict-filenames',
236             action='store_true', dest='restrictfilenames',
237             help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
238     filesystem.add_option('-a', '--batch-file',
239             dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
240     filesystem.add_option('-w', '--no-overwrites',
241             action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
242     filesystem.add_option('-c', '--continue',
243             action='store_true', dest='continue_dl', help='resume partially downloaded files', default=True)
244     filesystem.add_option('--no-continue',
245             action='store_false', dest='continue_dl',
246             help='do not resume partially downloaded files (restart from beginning)')
247     filesystem.add_option('--cookies',
248             dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
249     filesystem.add_option('--no-part',
250             action='store_true', dest='nopart', help='do not use .part files', default=False)
251     filesystem.add_option('--no-mtime',
252             action='store_false', dest='updatetime',
253             help='do not use the Last-modified header to set the file modification time', default=True)
254     filesystem.add_option('--write-description',
255             action='store_true', dest='writedescription',
256             help='write video description to a .description file', default=False)
257     filesystem.add_option('--write-info-json',
258             action='store_true', dest='writeinfojson',
259             help='write video metadata to a .info.json file', default=False)
260
261
262     postproc.add_option('-x', '--extract-audio', action='store_true', dest='extractaudio', default=False,
263             help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
264     postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
265             help='"best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; best by default')
266     postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
267             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)')
268     postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
269             help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)')
270     postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
271             help='keeps the video file on disk after the post-processing; the video is erased by default')
272     postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
273             help='do not overwrite post-processed files; the post-processed files are overwritten by default')
274
275
276     parser.add_option_group(general)
277     parser.add_option_group(selection)
278     parser.add_option_group(filesystem)
279     parser.add_option_group(verbosity)
280     parser.add_option_group(video_format)
281     parser.add_option_group(authentication)
282     parser.add_option_group(postproc)
283
284     xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
285     if xdg_config_home:
286         userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
287     else:
288         userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
289     argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
290     opts, args = parser.parse_args(argv)
291
292     return parser, opts, args
293
294 def _real_main():
295     parser, opts, args = parseOpts()
296
297     # Open appropriate CookieJar
298     if opts.cookiefile is None:
299         jar = compat_cookiejar.CookieJar()
300     else:
301         try:
302             jar = compat_cookiejar.MozillaCookieJar(opts.cookiefile)
303             if os.access(opts.cookiefile, os.R_OK):
304                 jar.load()
305         except (IOError, OSError) as err:
306             if opts.verbose:
307                 traceback.print_exc()
308             sys.stderr.write(u'ERROR: unable to open cookie file\n')
309             sys.exit(101)
310     # Set user agent
311     if opts.user_agent is not None:
312         std_headers['User-Agent'] = opts.user_agent
313
314     # Dump user agent
315     if opts.dump_user_agent:
316         print(std_headers['User-Agent'])
317         sys.exit(0)
318
319     # Batch file verification
320     batchurls = []
321     if opts.batchfile is not None:
322         try:
323             if opts.batchfile == '-':
324                 batchfd = sys.stdin
325             else:
326                 batchfd = open(opts.batchfile, 'r')
327             batchurls = batchfd.readlines()
328             batchurls = [x.strip() for x in batchurls]
329             batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)]
330         except IOError:
331             sys.exit(u'ERROR: batch file could not be read')
332     all_urls = batchurls + args
333     all_urls = [url.strip() for url in all_urls]
334
335     # General configuration
336     cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
337     proxy_handler = compat_urllib_request.ProxyHandler()
338     opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
339     compat_urllib_request.install_opener(opener)
340     socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
341
342     extractors = gen_extractors()
343
344     if opts.list_extractors:
345         for ie in extractors:
346             print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
347             matchedUrls = [url for url in all_urls if ie.suitable(url)]
348             all_urls = [url for url in all_urls if url not in matchedUrls]
349             for mu in matchedUrls:
350                 print(u'  ' + mu)
351         sys.exit(0)
352
353     # Conflicting, missing and erroneous options
354     if opts.usenetrc and (opts.username is not None or opts.password is not None):
355         parser.error(u'using .netrc conflicts with giving username/password')
356     if opts.password is not None and opts.username is None:
357         parser.error(u'account username missing')
358     if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
359         parser.error(u'using output template conflicts with using title, video ID or auto number')
360     if opts.usetitle and opts.useid:
361         parser.error(u'using title conflicts with using video ID')
362     if opts.username is not None and opts.password is None:
363         opts.password = getpass.getpass(u'Type account password and press return:')
364     if opts.ratelimit is not None:
365         numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
366         if numeric_limit is None:
367             parser.error(u'invalid rate limit specified')
368         opts.ratelimit = numeric_limit
369     if opts.min_filesize is not None:
370         numeric_limit = FileDownloader.parse_bytes(opts.min_filesize)
371         if numeric_limit is None:
372             parser.error(u'invalid min_filesize specified')
373         opts.min_filesize = numeric_limit
374     if opts.max_filesize is not None:
375         numeric_limit = FileDownloader.parse_bytes(opts.max_filesize)
376         if numeric_limit is None:
377             parser.error(u'invalid max_filesize specified')
378         opts.max_filesize = numeric_limit
379     if opts.retries is not None:
380         try:
381             opts.retries = int(opts.retries)
382         except (TypeError, ValueError) as err:
383             parser.error(u'invalid retry count specified')
384     if opts.buffersize is not None:
385         numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
386         if numeric_buffersize is None:
387             parser.error(u'invalid buffer size specified')
388         opts.buffersize = numeric_buffersize
389     try:
390         opts.playliststart = int(opts.playliststart)
391         if opts.playliststart <= 0:
392             raise ValueError(u'Playlist start must be positive')
393     except (TypeError, ValueError) as err:
394         parser.error(u'invalid playlist start number specified')
395     try:
396         opts.playlistend = int(opts.playlistend)
397         if opts.playlistend != -1 and (opts.playlistend <= 0 or opts.playlistend < opts.playliststart):
398             raise ValueError(u'Playlist end must be greater than playlist start')
399     except (TypeError, ValueError) as err:
400         parser.error(u'invalid playlist end number specified')
401     if opts.extractaudio:
402         if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
403             parser.error(u'invalid audio format specified')
404     if opts.audioquality:
405         opts.audioquality = opts.audioquality.strip('k').strip('K')
406         if not opts.audioquality.isdigit():
407             parser.error(u'invalid audio quality specified')
408     if opts.recodevideo is not None:
409         if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
410             parser.error(u'invalid video recode format specified')
411
412     if sys.version_info < (3,):
413         # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
414         if opts.outtmpl is not None:
415             opts.outtmpl = opts.outtmpl.decode(preferredencoding())
416     outtmpl =((opts.outtmpl is not None and opts.outtmpl)
417             or (opts.format == '-1' and opts.usetitle and u'%(title)s-%(id)s-%(format)s.%(ext)s')
418             or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
419             or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
420             or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
421             or (opts.useid and u'%(id)s.%(ext)s')
422             or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
423             or u'%(id)s.%(ext)s')
424
425     # File downloader
426     fd = FileDownloader({
427         'usenetrc': opts.usenetrc,
428         'username': opts.username,
429         'password': opts.password,
430         'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
431         'forceurl': opts.geturl,
432         'forcetitle': opts.gettitle,
433         'forcethumbnail': opts.getthumbnail,
434         'forcedescription': opts.getdescription,
435         'forcefilename': opts.getfilename,
436         'forceformat': opts.getformat,
437         'simulate': opts.simulate,
438         '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),
439         'format': opts.format,
440         'format_limit': opts.format_limit,
441         'listformats': opts.listformats,
442         'outtmpl': outtmpl,
443         'restrictfilenames': opts.restrictfilenames,
444         'ignoreerrors': opts.ignoreerrors,
445         'ratelimit': opts.ratelimit,
446         'nooverwrites': opts.nooverwrites,
447         'retries': opts.retries,
448         'buffersize': opts.buffersize,
449         'noresizebuffer': opts.noresizebuffer,
450         'continuedl': opts.continue_dl,
451         'noprogress': opts.noprogress,
452         'progress_with_newline': opts.progress_with_newline,
453         'playliststart': opts.playliststart,
454         'playlistend': opts.playlistend,
455         'logtostderr': opts.outtmpl == '-',
456         'consoletitle': opts.consoletitle,
457         'nopart': opts.nopart,
458         'updatetime': opts.updatetime,
459         'writedescription': opts.writedescription,
460         'writeinfojson': opts.writeinfojson,
461         'writesubtitles': opts.writesubtitles,
462         'onlysubtitles': opts.onlysubtitles,
463         'allsubtitles': opts.allsubtitles,
464         'subtitlesformat': opts.subtitlesformat,
465         'subtitleslang': opts.subtitleslang,
466         'matchtitle': decodeOption(opts.matchtitle),
467         'rejecttitle': decodeOption(opts.rejecttitle),
468         'max_downloads': opts.max_downloads,
469         'prefer_free_formats': opts.prefer_free_formats,
470         'verbose': opts.verbose,
471         'test': opts.test,
472         'keepvideo': opts.keepvideo,
473         'min_filesize': opts.min_filesize,
474         'max_filesize': opts.max_filesize
475         })
476
477     if opts.verbose:
478         fd.to_screen(u'[debug] youtube-dl version ' + __version__)
479         try:
480             sp = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
481                                   cwd=os.path.dirname(os.path.abspath(__file__)))
482             out, err = sp.communicate()
483             out = out.decode().strip()
484             if re.match('[0-9a-f]+', out):
485                 fd.to_screen(u'[debug] Git HEAD: ' + out)
486         except:
487             pass
488         fd.to_screen(u'[debug] Python version %s - %s' %(platform.python_version(), platform.platform()))
489         fd.to_screen(u'[debug] Proxy map: ' + str(proxy_handler.proxies))
490
491     for extractor in extractors:
492         fd.add_info_extractor(extractor)
493
494     # PostProcessors
495     if opts.extractaudio:
496         fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
497     if opts.recodevideo:
498         fd.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
499
500     # Update version
501     if opts.update_self:
502         update_self(fd.to_screen, opts.verbose, sys.argv[0])
503
504     # Maybe do nothing
505     if len(all_urls) < 1:
506         if not opts.update_self:
507             parser.error(u'you must provide at least one URL')
508         else:
509             sys.exit()
510
511     try:
512         retcode = fd.download(all_urls)
513     except MaxDownloadsReached:
514         fd.to_screen(u'--max-download limit reached, aborting.')
515         retcode = 101
516
517     # Dump cookie jar if requested
518     if opts.cookiefile is not None:
519         try:
520             jar.save()
521         except (IOError, OSError) as err:
522             sys.exit(u'ERROR: unable to save cookie jar')
523
524     sys.exit(retcode)
525
526 def main():
527     try:
528         _real_main()
529     except DownloadError:
530         sys.exit(1)
531     except SameFileError:
532         sys.exit(u'ERROR: fixed output name but more than one file to download')
533     except KeyboardInterrupt:
534         sys.exit(u'\nERROR: Interrupted by user')