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