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