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