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