Updated --no-resize-buffer docs, removed -b option
[youtube-dl] / youtube_dl / __init__.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 __authors__  = (
5         'Ricardo Garcia Gonzalez',
6         'Danny Colligan',
7         'Benjamin Johnson',
8         'Vasyl\' Vavrychuk',
9         'Witold Baryluk',
10         'Paweł Paprota',
11         'Gergely Imreh',
12         'Rogério Brito',
13         'Philipp Hagemeister',
14         'Sören Schulze',
15         'Kevin Ngo',
16         'Ori Avtalion',
17         'shizeeg',
18         'Filippo Valsorda',
19         )
20
21 __license__ = 'Public Domain'
22 __version__ = '2012.02.27'
23
24 UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
25 UPDATE_URL_VERSION = 'https://raw.github.com/rg3/youtube-dl/master/LATEST_VERSION'
26 UPDATE_URL_EXE = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl.exe'
27
28
29 import cookielib
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 urllib2
39 import warnings
40
41 from utils import *
42 from FileDownloader import *
43 from InfoExtractors import *
44 from PostProcessor import *
45
46 def updateSelf(downloader, filename):
47         ''' Update the program file with the latest version from the repository '''
48         # Note: downloader only used for options
49         
50         if not os.access(filename, os.W_OK):
51                 sys.exit('ERROR: no write permissions on %s' % filename)
52
53         downloader.to_screen(u'Updating to latest version...')
54
55         urlv = urllib2.urlopen(UPDATE_URL_VERSION)
56         newversion = urlv.read().strip()
57         if newversion == __version__:
58                 downloader.to_screen(u'youtube-dl is up-to-date (' + __version__ + ')')
59                 return
60         urlv.close()
61
62         if hasattr(sys, "frozen"): #py2exe
63                 exe = os.path.abspath(filename)
64                 directory = os.path.dirname(exe)
65                 if not os.access(directory, os.W_OK):
66                         sys.exit('ERROR: no write permissions on %s' % directory)
67                         
68                 try:
69                         urlh = urllib2.urlopen(UPDATE_URL_EXE)
70                         newcontent = urlh.read()
71                         urlh.close()
72                         with open(exe + '.new', 'wb') as outf:
73                                 outf.write(newcontent)
74                 except (IOError, OSError), err:
75                         sys.exit('ERROR: unable to download latest version')
76                         
77                 try:
78                         bat = os.path.join(directory, 'youtube-dl-updater.bat')
79                         b = open(bat, 'w')
80                         
81                         print >> b, """
82 echo Updating youtube-dl...
83 ping 127.0.0.1 -n 5 -w 1000 > NUL
84 move /Y "%s.new" "%s"
85 del "%s"
86                         """ %(exe, exe, bat)
87                         
88                         b.close()
89                         
90                         os.startfile(bat)
91                 except (IOError, OSError), err:
92                         sys.exit('ERROR: unable to overwrite current version')
93
94         else:
95                 try:
96                         urlh = urllib2.urlopen(UPDATE_URL)
97                         newcontent = urlh.read()
98                         urlh.close()
99                 except (IOError, OSError), err:
100                         sys.exit('ERROR: unable to download latest version')
101
102                 try:
103                         with open(filename, 'wb') as outf:
104                                 outf.write(newcontent)
105                 except (IOError, OSError), err:
106                         sys.exit('ERROR: unable to overwrite current version')
107
108         downloader.to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
109
110 def parseOpts():
111         def _readOptions(filename_bytes):
112                 try:
113                         optionf = open(filename_bytes)
114                 except IOError:
115                         return [] # silently skip if file is not present
116                 try:
117                         res = []
118                         for l in optionf:
119                                 res += shlex.split(l, comments=True)
120                 finally:
121                         optionf.close()
122                 return res
123
124         def _format_option_string(option):
125                 ''' ('-o', '--option') -> -o, --format METAVAR'''
126
127                 opts = []
128
129                 if option._short_opts: opts.append(option._short_opts[0])
130                 if option._long_opts: opts.append(option._long_opts[0])
131                 if len(opts) > 1: opts.insert(1, ', ')
132
133                 if option.takes_value(): opts.append(' %s' % option.metavar)
134
135                 return "".join(opts)
136
137         def _find_term_columns():
138                 columns = os.environ.get('COLUMNS', None)
139                 if columns:
140                         return int(columns)
141
142                 try:
143                         sp = subprocess.Popen(['stty', 'size'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
144                         out,err = sp.communicate()
145                         return int(out.split()[1])
146                 except:
147                         pass
148                 return None
149
150         max_width = 80
151         max_help_position = 80
152
153         # No need to wrap help messages if we're on a wide console
154         columns = _find_term_columns()
155         if columns: max_width = columns
156
157         fmt = optparse.IndentedHelpFormatter(width=max_width, max_help_position=max_help_position)
158         fmt.format_option_strings = _format_option_string
159
160         kw = {
161                 'version'   : __version__,
162                 'formatter' : fmt,
163                 'usage' : '%prog [options] url [url...]',
164                 'conflict_handler' : 'resolve',
165         }
166
167         parser = optparse.OptionParser(**kw)
168
169         # option groups
170         general        = optparse.OptionGroup(parser, 'General Options')
171         selection      = optparse.OptionGroup(parser, 'Video Selection')
172         authentication = optparse.OptionGroup(parser, 'Authentication Options')
173         video_format   = optparse.OptionGroup(parser, 'Video Format Options')
174         postproc       = optparse.OptionGroup(parser, 'Post-processing Options')
175         filesystem     = optparse.OptionGroup(parser, 'Filesystem Options')
176         verbosity      = optparse.OptionGroup(parser, 'Verbosity / Simulation Options')
177
178         general.add_option('-h', '--help',
179                         action='help', help='print this help text and exit')
180         general.add_option('-v', '--version',
181                         action='version', help='print program version and exit')
182         general.add_option('-U', '--update',
183                         action='store_true', dest='update_self', help='update this program to latest version')
184         general.add_option('-i', '--ignore-errors',
185                         action='store_true', dest='ignoreerrors', help='continue on download errors', default=False)
186         general.add_option('-r', '--rate-limit',
187                         dest='ratelimit', metavar='LIMIT', help='download rate limit (e.g. 50k or 44.6m)')
188         general.add_option('-R', '--retries',
189                         dest='retries', metavar='RETRIES', help='number of retries (default is 10)', default=10)
190         general.add_option('--buffer-size',
191                         dest='buffersize', metavar='SIZE', help='size of download buffer (e.g. 1024 or 16k) (default is 1024)', default="1024")
192         general.add_option('--no-resize-buffer',
193                         action='store_true', dest='noresizebuffer',
194                         help='do not automatically adjust the buffer size. By default, the buffer size is automatically resized from an initial value of SIZE.', default=False)
195         general.add_option('--dump-user-agent',
196                         action='store_true', dest='dump_user_agent',
197                         help='display the current browser identification', default=False)
198         general.add_option('--list-extractors',
199                         action='store_true', dest='list_extractors',
200                         help='List all supported extractors and the URLs they would handle', default=False)
201
202         selection.add_option('--playlist-start',
203                         dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is 1)', default=1)
204         selection.add_option('--playlist-end',
205                         dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
206         selection.add_option('--match-title', dest='matchtitle', metavar='REGEX',help='download only matching titles (regex or caseless sub-string)')
207         selection.add_option('--reject-title', dest='rejecttitle', metavar='REGEX',help='skip download for matching titles (regex or caseless sub-string)')
208         selection.add_option('--max-downloads', metavar='NUMBER', dest='max_downloads', help='Abort after downloading NUMBER files', default=None)
209
210         authentication.add_option('-u', '--username',
211                         dest='username', metavar='USERNAME', help='account username')
212         authentication.add_option('-p', '--password',
213                         dest='password', metavar='PASSWORD', help='account password')
214         authentication.add_option('-n', '--netrc',
215                         action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
216
217
218         video_format.add_option('-f', '--format',
219                         action='store', dest='format', metavar='FORMAT', help='video format code')
220         video_format.add_option('--all-formats',
221                         action='store_const', dest='format', help='download all available video formats', const='all')
222         video_format.add_option('--prefer-free-formats',
223                         action='store_true', dest='prefer_free_formats', default=False, help='prefer free video formats unless a specific one is requested')
224         video_format.add_option('--max-quality',
225                         action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
226         video_format.add_option('-F', '--list-formats',
227                         action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
228         video_format.add_option('--write-srt',
229                         action='store_true', dest='writesubtitles',
230                         help='write video closed captions to a .srt file (currently youtube only)', default=False)
231         video_format.add_option('--srt-lang',
232                         action='store', dest='subtitleslang', metavar='LANG',
233                         help='language of the closed captions to download (optional) use IETF language tags like \'en\'')
234
235
236         verbosity.add_option('-q', '--quiet',
237                         action='store_true', dest='quiet', help='activates quiet mode', default=False)
238         verbosity.add_option('-s', '--simulate',
239                         action='store_true', dest='simulate', help='do not download the video and do not write anything to disk', default=False)
240         verbosity.add_option('--skip-download',
241                         action='store_true', dest='skip_download', help='do not download the video', default=False)
242         verbosity.add_option('-g', '--get-url',
243                         action='store_true', dest='geturl', help='simulate, quiet but print URL', default=False)
244         verbosity.add_option('-e', '--get-title',
245                         action='store_true', dest='gettitle', help='simulate, quiet but print title', default=False)
246         verbosity.add_option('--get-thumbnail',
247                         action='store_true', dest='getthumbnail',
248                         help='simulate, quiet but print thumbnail URL', default=False)
249         verbosity.add_option('--get-description',
250                         action='store_true', dest='getdescription',
251                         help='simulate, quiet but print video description', default=False)
252         verbosity.add_option('--get-filename',
253                         action='store_true', dest='getfilename',
254                         help='simulate, quiet but print output filename', default=False)
255         verbosity.add_option('--get-format',
256                         action='store_true', dest='getformat',
257                         help='simulate, quiet but print output format', default=False)
258         verbosity.add_option('--no-progress',
259                         action='store_true', dest='noprogress', help='do not print progress bar', default=False)
260         verbosity.add_option('--console-title',
261                         action='store_true', dest='consoletitle',
262                         help='display progress in console titlebar', default=False)
263         verbosity.add_option('-v', '--verbose',
264                         action='store_true', dest='verbose', help='print various debugging information', default=False)
265
266
267         filesystem.add_option('-t', '--title',
268                         action='store_true', dest='usetitle', help='use title in file name', default=False)
269         filesystem.add_option('-l', '--literal',
270                         action='store_true', dest='useliteral', help='use literal title in file name', default=False)
271         filesystem.add_option('-A', '--auto-number',
272                         action='store_true', dest='autonumber',
273                         help='number downloaded files starting from 00000', default=False)
274         filesystem.add_option('-o', '--output',
275                         dest='outtmpl', metavar='TEMPLATE', help='output filename template. Use %(stitle)s to get the title, %(uploader)s for the uploader name, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(upload_date)s for the upload date (YYYYMMDD), and %% for a literal percent. Use - to output to stdout.')
276         filesystem.add_option('-a', '--batch-file',
277                         dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
278         filesystem.add_option('-w', '--no-overwrites',
279                         action='store_true', dest='nooverwrites', help='do not overwrite files', default=False)
280         filesystem.add_option('-c', '--continue',
281                         action='store_true', dest='continue_dl', help='resume partially downloaded files', default=True)
282         filesystem.add_option('--no-continue',
283                         action='store_false', dest='continue_dl',
284                         help='do not resume partially downloaded files (restart from beginning)')
285         filesystem.add_option('--cookies',
286                         dest='cookiefile', metavar='FILE', help='file to read cookies from and dump cookie jar in')
287         filesystem.add_option('--no-part',
288                         action='store_true', dest='nopart', help='do not use .part files', default=False)
289         filesystem.add_option('--no-mtime',
290                         action='store_false', dest='updatetime',
291                         help='do not use the Last-modified header to set the file modification time', default=True)
292         filesystem.add_option('--write-description',
293                         action='store_true', dest='writedescription',
294                         help='write video description to a .description file', default=False)
295         filesystem.add_option('--write-info-json',
296                         action='store_true', dest='writeinfojson',
297                         help='write video metadata to a .info.json file', default=False)
298
299
300         postproc.add_option('--extract-audio', action='store_true', dest='extractaudio', default=False,
301                         help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
302         postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
303                         help='"best", "aac", "vorbis", "mp3", "m4a", or "wav"; best by default')
304         postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='128K',
305                         help='ffmpeg/avconv audio bitrate specification, 128k by default')
306         postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
307                         help='keeps the video file on disk after the post-processing; the video is erased by default')
308
309
310         parser.add_option_group(general)
311         parser.add_option_group(selection)
312         parser.add_option_group(filesystem)
313         parser.add_option_group(verbosity)
314         parser.add_option_group(video_format)
315         parser.add_option_group(authentication)
316         parser.add_option_group(postproc)
317
318         xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
319         if xdg_config_home:
320                 userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
321         else:
322                 userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
323         argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
324         opts, args = parser.parse_args(argv)
325
326         return parser, opts, args
327
328 def gen_extractors():
329         """ Return a list of an instance of every supported extractor.
330         The order does matter; the first extractor matched is the one handling the URL.
331         """
332         return [
333                 YoutubePlaylistIE(),
334                 YoutubeUserIE(),
335                 YoutubeSearchIE(),
336                 YoutubeIE(),
337                 MetacafeIE(),
338                 DailymotionIE(),
339                 GoogleIE(),
340                 GoogleSearchIE(),
341                 PhotobucketIE(),
342                 YahooIE(),
343                 YahooSearchIE(),
344                 DepositFilesIE(),
345                 FacebookIE(),
346                 BlipTVIE(),
347                 VimeoIE(),
348                 MyVideoIE(),
349                 ComedyCentralIE(),
350                 EscapistIE(),
351                 CollegeHumorIE(),
352                 XVideosIE(),
353                 SoundcloudIE(),
354                 InfoQIE(),
355                 MixcloudIE(),
356                 StanfordOpenClassroomIE(),
357                 MTVIE(),
358
359                 GenericIE()
360         ]
361
362 def _real_main():
363         parser, opts, args = parseOpts()
364
365         # Open appropriate CookieJar
366         if opts.cookiefile is None:
367                 jar = cookielib.CookieJar()
368         else:
369                 try:
370                         jar = cookielib.MozillaCookieJar(opts.cookiefile)
371                         if os.path.isfile(opts.cookiefile) and os.access(opts.cookiefile, os.R_OK):
372                                 jar.load()
373                 except (IOError, OSError), err:
374                         sys.exit(u'ERROR: unable to open cookie file')
375
376         # Dump user agent
377         if opts.dump_user_agent:
378                 print std_headers['User-Agent']
379                 sys.exit(0)
380
381         # Batch file verification
382         batchurls = []
383         if opts.batchfile is not None:
384                 try:
385                         if opts.batchfile == '-':
386                                 batchfd = sys.stdin
387                         else:
388                                 batchfd = open(opts.batchfile, 'r')
389                         batchurls = batchfd.readlines()
390                         batchurls = [x.strip() for x in batchurls]
391                         batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)]
392                 except IOError:
393                         sys.exit(u'ERROR: batch file could not be read')
394         all_urls = batchurls + args
395         all_urls = map(lambda url: url.strip(), all_urls)
396
397         # General configuration
398         cookie_processor = urllib2.HTTPCookieProcessor(jar)
399         proxy_handler = urllib2.ProxyHandler()
400         opener = urllib2.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
401         urllib2.install_opener(opener)
402         socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
403
404         extractors = gen_extractors()
405
406         if opts.list_extractors:
407                 for ie in extractors:
408                         print(ie.IE_NAME)
409                         matchedUrls = filter(lambda url: ie.suitable(url), all_urls)
410                         all_urls = filter(lambda url: url not in matchedUrls, all_urls)
411                         for mu in matchedUrls:
412                                 print(u'  ' + mu)
413                 sys.exit(0)
414
415         # Conflicting, missing and erroneous options
416         if opts.usenetrc and (opts.username is not None or opts.password is not None):
417                 parser.error(u'using .netrc conflicts with giving username/password')
418         if opts.password is not None and opts.username is None:
419                 parser.error(u'account username missing')
420         if opts.outtmpl is not None and (opts.useliteral or opts.usetitle or opts.autonumber):
421                 parser.error(u'using output template conflicts with using title, literal title or auto number')
422         if opts.usetitle and opts.useliteral:
423                 parser.error(u'using title conflicts with using literal title')
424         if opts.username is not None and opts.password is None:
425                 opts.password = getpass.getpass(u'Type account password and press return:')
426         if opts.ratelimit is not None:
427                 numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
428                 if numeric_limit is None:
429                         parser.error(u'invalid rate limit specified')
430                 opts.ratelimit = numeric_limit
431         if opts.retries is not None:
432                 try:
433                         opts.retries = long(opts.retries)
434                 except (TypeError, ValueError), err:
435                         parser.error(u'invalid retry count specified')
436         if opts.buffersize is not None:
437                 numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
438                 if numeric_buffersize is None:
439                         parser.error(u'invalid buffer size specified')
440                 opts.buffersize = numeric_buffersize
441         try:
442                 opts.playliststart = int(opts.playliststart)
443                 if opts.playliststart <= 0:
444                         raise ValueError(u'Playlist start must be positive')
445         except (TypeError, ValueError), err:
446                 parser.error(u'invalid playlist start number specified')
447         try:
448                 opts.playlistend = int(opts.playlistend)
449                 if opts.playlistend != -1 and (opts.playlistend <= 0 or opts.playlistend < opts.playliststart):
450                         raise ValueError(u'Playlist end must be greater than playlist start')
451         except (TypeError, ValueError), err:
452                 parser.error(u'invalid playlist end number specified')
453         if opts.extractaudio:
454                 if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis', 'm4a', 'wav']:
455                         parser.error(u'invalid audio format specified')
456
457         # File downloader
458         fd = FileDownloader({
459                 'usenetrc': opts.usenetrc,
460                 'username': opts.username,
461                 'password': opts.password,
462                 'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
463                 'forceurl': opts.geturl,
464                 'forcetitle': opts.gettitle,
465                 'forcethumbnail': opts.getthumbnail,
466                 'forcedescription': opts.getdescription,
467                 'forcefilename': opts.getfilename,
468                 'forceformat': opts.getformat,
469                 'simulate': opts.simulate,
470                 '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),
471                 'format': opts.format,
472                 'format_limit': opts.format_limit,
473                 'listformats': opts.listformats,
474                 'outtmpl': ((opts.outtmpl is not None and opts.outtmpl.decode(preferredencoding()))
475                         or (opts.format == '-1' and opts.usetitle and u'%(stitle)s-%(id)s-%(format)s.%(ext)s')
476                         or (opts.format == '-1' and opts.useliteral and u'%(title)s-%(id)s-%(format)s.%(ext)s')
477                         or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
478                         or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(stitle)s-%(id)s.%(ext)s')
479                         or (opts.useliteral and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
480                         or (opts.usetitle and u'%(stitle)s-%(id)s.%(ext)s')
481                         or (opts.useliteral and u'%(title)s-%(id)s.%(ext)s')
482                         or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
483                         or u'%(id)s.%(ext)s'),
484                 'ignoreerrors': opts.ignoreerrors,
485                 'ratelimit': opts.ratelimit,
486                 'nooverwrites': opts.nooverwrites,
487                 'retries': opts.retries,
488                 'buffersize': opts.buffersize,
489                 'noresizebuffer': opts.noresizebuffer,
490                 'continuedl': opts.continue_dl,
491                 'noprogress': opts.noprogress,
492                 'playliststart': opts.playliststart,
493                 'playlistend': opts.playlistend,
494                 'logtostderr': opts.outtmpl == '-',
495                 'consoletitle': opts.consoletitle,
496                 'nopart': opts.nopart,
497                 'updatetime': opts.updatetime,
498                 'writedescription': opts.writedescription,
499                 'writeinfojson': opts.writeinfojson,
500                 'writesubtitles': opts.writesubtitles,
501                 'subtitleslang': opts.subtitleslang,
502                 'matchtitle': opts.matchtitle,
503                 'rejecttitle': opts.rejecttitle,
504                 'max_downloads': opts.max_downloads,
505                 'prefer_free_formats': opts.prefer_free_formats,
506                 'verbose': opts.verbose,
507                 })
508
509         if opts.verbose:
510                 fd.to_screen(u'[debug] Proxy map: ' + str(proxy_handler.proxies))
511
512         for extractor in extractors:
513                 fd.add_info_extractor(extractor)
514
515         # PostProcessors
516         if opts.extractaudio:
517                 fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, keepvideo=opts.keepvideo))
518
519         # Update version
520         if opts.update_self:
521                 updateSelf(fd, sys.argv[0])
522
523         # Maybe do nothing
524         if len(all_urls) < 1:
525                 if not opts.update_self:
526                         parser.error(u'you must provide at least one URL')
527                 else:
528                         sys.exit()
529         
530         try:
531                 retcode = fd.download(all_urls)
532         except MaxDownloadsReached:
533                 fd.to_screen(u'--max-download limit reached, aborting.')
534                 retcode = 101
535
536         # Dump cookie jar if requested
537         if opts.cookiefile is not None:
538                 try:
539                         jar.save()
540                 except (IOError, OSError), err:
541                         sys.exit(u'ERROR: unable to save cookie jar')
542
543         sys.exit(retcode)
544
545 def main():
546         try:
547                 _real_main()
548         except DownloadError:
549                 sys.exit(1)
550         except SameFileError:
551                 sys.exit(u'ERROR: fixed output name but more than one file to download')
552         except KeyboardInterrupt:
553                 sys.exit(u'\nERROR: Interrupted by user')