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