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