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