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