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