Improve cookie error handling
[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     selection.add_option('--min-filesize', metavar='SIZE', dest='min_filesize', help="Skip files smaller than this size", default=None)
155     selection.add_option('--max-filesize', metavar='SIZE', dest='max_filesize', help="Skip files larger than this size", 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('--no-progress',
206             action='store_true', dest='noprogress', help='do not print progress bar', default=False)
207     verbosity.add_option('--console-title',
208             action='store_true', dest='consoletitle',
209             help='display progress in console titlebar', default=False)
210     verbosity.add_option('-v', '--verbose',
211             action='store_true', dest='verbose', help='print various debugging information', default=False)
212
213
214     filesystem.add_option('-t', '--title',
215             action='store_true', dest='usetitle', help='use title in file name', default=False)
216     filesystem.add_option('--id',
217             action='store_true', dest='useid', help='use video ID in file name', default=False)
218     filesystem.add_option('-l', '--literal',
219             action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
220     filesystem.add_option('-A', '--auto-number',
221             action='store_true', dest='autonumber',
222             help='number downloaded files starting from 00000', default=False)
223     filesystem.add_option('-o', '--output',
224             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\' .')
225     filesystem.add_option('--restrict-filenames',
226             action='store_true', dest='restrictfilenames',
227             help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
228     filesystem.add_option('-a', '--batch-file',
229             dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
230     filesystem.add_option('-w', '--no-overwrites',
231             action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
232     filesystem.add_option('-c', '--continue',
233             action='store_true', dest='continue_dl', help='resume partially downloaded files', default=True)
234     filesystem.add_option('--no-continue',
235             action='store_false', dest='continue_dl',
236             help='do not resume partially downloaded files (restart from beginning)')
237     filesystem.add_option('--cookies',
238             dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
239     filesystem.add_option('--no-part',
240             action='store_true', dest='nopart', help='do not use .part files', default=False)
241     filesystem.add_option('--no-mtime',
242             action='store_false', dest='updatetime',
243             help='do not use the Last-modified header to set the file modification time', default=True)
244     filesystem.add_option('--write-description',
245             action='store_true', dest='writedescription',
246             help='write video description to a .description file', default=False)
247     filesystem.add_option('--write-info-json',
248             action='store_true', dest='writeinfojson',
249             help='write video metadata to a .info.json file', default=False)
250
251
252     postproc.add_option('-x', '--extract-audio', action='store_true', dest='extractaudio', default=False,
253             help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
254     postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
255             help='"best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; best by default')
256     postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
257             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)')
258     postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
259             help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)')
260     postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
261             help='keeps the video file on disk after the post-processing; the video is erased by default')
262     postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
263             help='do not overwrite post-processed files; the post-processed files are overwritten by default')
264
265
266     parser.add_option_group(general)
267     parser.add_option_group(selection)
268     parser.add_option_group(filesystem)
269     parser.add_option_group(verbosity)
270     parser.add_option_group(video_format)
271     parser.add_option_group(authentication)
272     parser.add_option_group(postproc)
273
274     xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
275     if xdg_config_home:
276         userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
277     else:
278         userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
279     argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
280     opts, args = parser.parse_args(argv)
281
282     return parser, opts, args
283
284 def _real_main():
285     parser, opts, args = parseOpts()
286
287     # Open appropriate CookieJar
288     if opts.cookiefile is None:
289         jar = compat_cookiejar.CookieJar()
290     else:
291         try:
292             jar = compat_cookiejar.MozillaCookieJar(opts.cookiefile)
293             if os.access(opts.cookiefile, os.R_OK):
294                 jar.load()
295         except (IOError, OSError) as err:
296             if opts.verbose:
297                 traceback.print_exc()
298             sys.stderr.write(u'ERROR: unable to open cookie file\n')
299             sys.exit(101)
300     # Set user agent
301     if opts.user_agent is not None:
302         std_headers['User-Agent'] = opts.user_agent
303
304     # Dump user agent
305     if opts.dump_user_agent:
306         print(std_headers['User-Agent'])
307         sys.exit(0)
308
309     # Batch file verification
310     batchurls = []
311     if opts.batchfile is not None:
312         try:
313             if opts.batchfile == '-':
314                 batchfd = sys.stdin
315             else:
316                 batchfd = open(opts.batchfile, 'r')
317             batchurls = batchfd.readlines()
318             batchurls = [x.strip() for x in batchurls]
319             batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)]
320         except IOError:
321             sys.exit(u'ERROR: batch file could not be read')
322     all_urls = batchurls + args
323     all_urls = [url.strip() for url in all_urls]
324
325     # General configuration
326     cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
327     proxy_handler = compat_urllib_request.ProxyHandler()
328     opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
329     compat_urllib_request.install_opener(opener)
330     socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
331
332     extractors = gen_extractors()
333
334     if opts.list_extractors:
335         for ie in extractors:
336             print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
337             matchedUrls = [url for url in all_urls if ie.suitable(url)]
338             all_urls = [url for url in all_urls if url not in matchedUrls]
339             for mu in matchedUrls:
340                 print(u'  ' + mu)
341         sys.exit(0)
342
343     # Conflicting, missing and erroneous options
344     if opts.usenetrc and (opts.username is not None or opts.password is not None):
345         parser.error(u'using .netrc conflicts with giving username/password')
346     if opts.password is not None and opts.username is None:
347         parser.error(u'account username missing')
348     if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
349         parser.error(u'using output template conflicts with using title, video ID or auto number')
350     if opts.usetitle and opts.useid:
351         parser.error(u'using title conflicts with using video ID')
352     if opts.username is not None and opts.password is None:
353         opts.password = getpass.getpass(u'Type account password and press return:')
354     if opts.ratelimit is not None:
355         numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
356         if numeric_limit is None:
357             parser.error(u'invalid rate limit specified')
358         opts.ratelimit = numeric_limit
359     if opts.min_filesize is not None:
360         numeric_limit = FileDownloader.parse_bytes(opts.min_filesize)
361         if numeric_limit is None:
362             parser.error(u'invalid min_filesize specified')
363         opts.min_filesize = numeric_limit
364     if opts.max_filesize is not None:
365         numeric_limit = FileDownloader.parse_bytes(opts.max_filesize)
366         if numeric_limit is None:
367             parser.error(u'invalid max_filesize specified')
368         opts.max_filesize = numeric_limit
369     if opts.retries is not None:
370         try:
371             opts.retries = int(opts.retries)
372         except (TypeError, ValueError) as err:
373             parser.error(u'invalid retry count specified')
374     if opts.buffersize is not None:
375         numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
376         if numeric_buffersize is None:
377             parser.error(u'invalid buffer size specified')
378         opts.buffersize = numeric_buffersize
379     try:
380         opts.playliststart = int(opts.playliststart)
381         if opts.playliststart <= 0:
382             raise ValueError(u'Playlist start must be positive')
383     except (TypeError, ValueError) as err:
384         parser.error(u'invalid playlist start number specified')
385     try:
386         opts.playlistend = int(opts.playlistend)
387         if opts.playlistend != -1 and (opts.playlistend <= 0 or opts.playlistend < opts.playliststart):
388             raise ValueError(u'Playlist end must be greater than playlist start')
389     except (TypeError, ValueError) as err:
390         parser.error(u'invalid playlist end number specified')
391     if opts.extractaudio:
392         if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
393             parser.error(u'invalid audio format specified')
394     if opts.audioquality:
395         opts.audioquality = opts.audioquality.strip('k').strip('K')
396         if not opts.audioquality.isdigit():
397             parser.error(u'invalid audio quality specified')
398     if opts.recodevideo is not None:
399         if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
400             parser.error(u'invalid video recode format specified')
401
402     if sys.version_info < (3,):
403         # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
404         if opts.outtmpl is not None:
405             opts.outtmpl = opts.outtmpl.decode(preferredencoding())
406     outtmpl =((opts.outtmpl is not None and opts.outtmpl)
407             or (opts.format == '-1' and opts.usetitle and u'%(title)s-%(id)s-%(format)s.%(ext)s')
408             or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
409             or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
410             or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
411             or (opts.useid and u'%(id)s.%(ext)s')
412             or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
413             or u'%(id)s.%(ext)s')
414     # File downloader
415     fd = FileDownloader({
416         'usenetrc': opts.usenetrc,
417         'username': opts.username,
418         'password': opts.password,
419         'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
420         'forceurl': opts.geturl,
421         'forcetitle': opts.gettitle,
422         'forcethumbnail': opts.getthumbnail,
423         'forcedescription': opts.getdescription,
424         'forcefilename': opts.getfilename,
425         'forceformat': opts.getformat,
426         'simulate': opts.simulate,
427         '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),
428         'format': opts.format,
429         'format_limit': opts.format_limit,
430         'listformats': opts.listformats,
431         'outtmpl': outtmpl,
432         'restrictfilenames': opts.restrictfilenames,
433         'ignoreerrors': opts.ignoreerrors,
434         'ratelimit': opts.ratelimit,
435         'nooverwrites': opts.nooverwrites,
436         'retries': opts.retries,
437         'buffersize': opts.buffersize,
438         'noresizebuffer': opts.noresizebuffer,
439         'continuedl': opts.continue_dl,
440         'noprogress': opts.noprogress,
441         'playliststart': opts.playliststart,
442         'playlistend': opts.playlistend,
443         'logtostderr': opts.outtmpl == '-',
444         'consoletitle': opts.consoletitle,
445         'nopart': opts.nopart,
446         'updatetime': opts.updatetime,
447         'writedescription': opts.writedescription,
448         'writeinfojson': opts.writeinfojson,
449         'writesubtitles': opts.writesubtitles,
450         'subtitleslang': opts.subtitleslang,
451         'matchtitle': opts.matchtitle,
452         'rejecttitle': opts.rejecttitle,
453         'max_downloads': opts.max_downloads,
454         'prefer_free_formats': opts.prefer_free_formats,
455         'verbose': opts.verbose,
456         'test': opts.test,
457         'keepvideo': opts.keepvideo,
458         'min_filesize': opts.min_filesize,
459         'max_filesize': opts.max_filesize
460         })
461
462     if opts.verbose:
463         fd.to_screen(u'[debug] youtube-dl version ' + __version__)
464         try:
465             sp = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
466                                   cwd=os.path.dirname(os.path.abspath(__file__)))
467             out, err = sp.communicate()
468             out = out.decode().strip()
469             if re.match('[0-9a-f]+', out):
470                 fd.to_screen(u'[debug] Git HEAD: ' + out)
471         except:
472             pass
473         fd.to_screen(u'[debug] Python version %s - %s' %(platform.python_version(), platform.platform()))
474         fd.to_screen(u'[debug] Proxy map: ' + str(proxy_handler.proxies))
475
476     for extractor in extractors:
477         fd.add_info_extractor(extractor)
478
479     # PostProcessors
480     if opts.extractaudio:
481         fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
482     if opts.recodevideo:
483         fd.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
484
485     # Update version
486     if opts.update_self:
487         update_self(fd.to_screen, opts.verbose, sys.argv[0])
488
489     # Maybe do nothing
490     if len(all_urls) < 1:
491         if not opts.update_self:
492             parser.error(u'you must provide at least one URL')
493         else:
494             sys.exit()
495
496     try:
497         retcode = fd.download(all_urls)
498     except MaxDownloadsReached:
499         fd.to_screen(u'--max-download limit reached, aborting.')
500         retcode = 101
501
502     # Dump cookie jar if requested
503     if opts.cookiefile is not None:
504         try:
505             jar.save()
506         except (IOError, OSError) as err:
507             sys.exit(u'ERROR: unable to save cookie jar')
508
509     sys.exit(retcode)
510
511 def main():
512     try:
513         _real_main()
514     except DownloadError:
515         sys.exit(1)
516     except SameFileError:
517         sys.exit(u'ERROR: fixed output name but more than one file to download')
518     except KeyboardInterrupt:
519         sys.exit(u'\nERROR: Interrupted by user')