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