]> jfr.im git - yt-dlp.git/blame - youtube_dl/__init__.py
Merge remote-tracking branch 'dcoppa/master'
[yt-dlp.git] / youtube_dl / __init__.py
CommitLineData
235b3ba4
PH
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
02b324a2 4from __future__ import with_statement
9e8056d5 5from __future__ import absolute_import
02b324a2 6
3906e6ce 7__authors__ = (
59ae15a5
PH
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',
88f6c78b 23 'Dave Vasilevsky',
2069acc6 24 'Jaime Marquínez Ferrándiz',
fffec3b9 25 'Jeff Crouse',
59ae15a5 26 )
235b3ba4
PH
27
28__license__ = 'Public Domain'
235b3ba4 29
c9ed14e6 30import getpass
c9ed14e6 31import optparse
235b3ba4 32import os
235b3ba4 33import re
c9ed14e6 34import shlex
235b3ba4 35import socket
235b3ba4
PH
36import subprocess
37import sys
235b3ba4 38import warnings
4e38899e 39import platform
235b3ba4 40
9e8056d5 41from .utils import *
d5ed35b6 42from .update import update_self
f427df17 43from .version import __version__
9e8056d5 44from .FileDownloader import *
4aeae91f 45from .InfoExtractors import gen_extractors
9e8056d5 46from .PostProcessor import *
235b3ba4 47
235b3ba4 48def parseOpts():
59ae15a5
PH
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)
8d5d3a5d 144 general.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
59ae15a5
PH
145
146 selection.add_option('--playlist-start',
147 dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is %default)', default=1)
148 selection.add_option('--playlist-end',
149 dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
150 selection.add_option('--match-title', dest='matchtitle', metavar='REGEX',help='download only matching titles (regex or caseless sub-string)')
151 selection.add_option('--reject-title', dest='rejecttitle', metavar='REGEX',help='skip download for matching titles (regex or caseless sub-string)')
152 selection.add_option('--max-downloads', metavar='NUMBER', dest='max_downloads', help='Abort after downloading NUMBER files', default=None)
153
154 authentication.add_option('-u', '--username',
155 dest='username', metavar='USERNAME', help='account username')
156 authentication.add_option('-p', '--password',
157 dest='password', metavar='PASSWORD', help='account password')
158 authentication.add_option('-n', '--netrc',
159 action='store_true', dest='usenetrc', help='use .netrc authentication data', default=False)
160
161
162 video_format.add_option('-f', '--format',
163 action='store', dest='format', metavar='FORMAT', help='video format code')
164 video_format.add_option('--all-formats',
165 action='store_const', dest='format', help='download all available video formats', const='all')
166 video_format.add_option('--prefer-free-formats',
167 action='store_true', dest='prefer_free_formats', default=False, help='prefer free video formats unless a specific one is requested')
168 video_format.add_option('--max-quality',
169 action='store', dest='format_limit', metavar='FORMAT', help='highest quality format to download')
170 video_format.add_option('-F', '--list-formats',
171 action='store_true', dest='listformats', help='list all available formats (currently youtube only)')
172 video_format.add_option('--write-srt',
173 action='store_true', dest='writesubtitles',
174 help='write video closed captions to a .srt file (currently youtube only)', default=False)
175 video_format.add_option('--srt-lang',
176 action='store', dest='subtitleslang', metavar='LANG',
177 help='language of the closed captions to download (optional) use IETF language tags like \'en\'')
178
59ae15a5
PH
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',
77c4beab 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\' .')
59ae15a5
PH
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',
510e6f6d 251 help='"best", "aac", "vorbis", "mp3", "m4a", "opus", or "wav"; best by default')
59ae15a5
PH
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)')
7851b379
PH
254 postproc.add_option('--recode-video', metavar='FORMAT', dest='recodevideo', default=None,
255 help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm)')
59ae15a5
PH
256 postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
257 help='keeps the video file on disk after the post-processing; the video is erased by default')
f0648fc1
BPG
258 postproc.add_option('--no-post-overwrites', action='store_true', dest='nopostoverwrites', default=False,
259 help='do not overwrite post-processed files; the post-processed files are overwritten by default')
59ae15a5
PH
260
261
262 parser.add_option_group(general)
263 parser.add_option_group(selection)
264 parser.add_option_group(filesystem)
265 parser.add_option_group(verbosity)
266 parser.add_option_group(video_format)
267 parser.add_option_group(authentication)
268 parser.add_option_group(postproc)
269
270 xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
271 if xdg_config_home:
272 userConf = os.path.join(xdg_config_home, 'youtube-dl.conf')
273 else:
274 userConf = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
275 argv = _readOptions('/etc/youtube-dl.conf') + _readOptions(userConf) + sys.argv[1:]
276 opts, args = parser.parse_args(argv)
277
278 return parser, opts, args
235b3ba4 279
235b3ba4 280def _real_main():
59ae15a5
PH
281 parser, opts, args = parseOpts()
282
283 # Open appropriate CookieJar
284 if opts.cookiefile is None:
285 jar = compat_cookiejar.CookieJar()
286 else:
287 try:
288 jar = compat_cookiejar.MozillaCookieJar(opts.cookiefile)
289 if os.path.isfile(opts.cookiefile) and os.access(opts.cookiefile, os.R_OK):
290 jar.load()
291 except (IOError, OSError) as err:
292 sys.exit(u'ERROR: unable to open cookie file')
293 # Set user agent
294 if opts.user_agent is not None:
295 std_headers['User-Agent'] = opts.user_agent
296
297 # Dump user agent
298 if opts.dump_user_agent:
299 print(std_headers['User-Agent'])
300 sys.exit(0)
301
302 # Batch file verification
303 batchurls = []
304 if opts.batchfile is not None:
305 try:
306 if opts.batchfile == '-':
307 batchfd = sys.stdin
308 else:
309 batchfd = open(opts.batchfile, 'r')
310 batchurls = batchfd.readlines()
311 batchurls = [x.strip() for x in batchurls]
312 batchurls = [x for x in batchurls if len(x) > 0 and not re.search(r'^[#/;]', x)]
313 except IOError:
314 sys.exit(u'ERROR: batch file could not be read')
315 all_urls = batchurls + args
316 all_urls = [url.strip() for url in all_urls]
317
318 # General configuration
319 cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
320 proxy_handler = compat_urllib_request.ProxyHandler()
321 opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
322 compat_urllib_request.install_opener(opener)
323 socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
324
325 extractors = gen_extractors()
326
327 if opts.list_extractors:
328 for ie in extractors:
b08e09c3 329 print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
1a2c3c0f
FV
330 matchedUrls = [url for url in all_urls if ie.suitable(url)]
331 all_urls = [url for url in all_urls if url not in matchedUrls]
59ae15a5
PH
332 for mu in matchedUrls:
333 print(u' ' + mu)
334 sys.exit(0)
335
336 # Conflicting, missing and erroneous options
337 if opts.usenetrc and (opts.username is not None or opts.password is not None):
338 parser.error(u'using .netrc conflicts with giving username/password')
339 if opts.password is not None and opts.username is None:
340 parser.error(u'account username missing')
341 if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
342 parser.error(u'using output template conflicts with using title, video ID or auto number')
343 if opts.usetitle and opts.useid:
344 parser.error(u'using title conflicts with using video ID')
345 if opts.username is not None and opts.password is None:
346 opts.password = getpass.getpass(u'Type account password and press return:')
347 if opts.ratelimit is not None:
348 numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
349 if numeric_limit is None:
350 parser.error(u'invalid rate limit specified')
351 opts.ratelimit = numeric_limit
352 if opts.retries is not None:
353 try:
354 opts.retries = int(opts.retries)
355 except (TypeError, ValueError) as err:
356 parser.error(u'invalid retry count specified')
357 if opts.buffersize is not None:
358 numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
359 if numeric_buffersize is None:
360 parser.error(u'invalid buffer size specified')
361 opts.buffersize = numeric_buffersize
362 try:
363 opts.playliststart = int(opts.playliststart)
364 if opts.playliststart <= 0:
365 raise ValueError(u'Playlist start must be positive')
366 except (TypeError, ValueError) as err:
367 parser.error(u'invalid playlist start number specified')
368 try:
369 opts.playlistend = int(opts.playlistend)
370 if opts.playlistend != -1 and (opts.playlistend <= 0 or opts.playlistend < opts.playliststart):
371 raise ValueError(u'Playlist end must be greater than playlist start')
372 except (TypeError, ValueError) as err:
373 parser.error(u'invalid playlist end number specified')
374 if opts.extractaudio:
510e6f6d 375 if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
59ae15a5
PH
376 parser.error(u'invalid audio format specified')
377 if opts.audioquality:
378 opts.audioquality = opts.audioquality.strip('k').strip('K')
379 if not opts.audioquality.isdigit():
380 parser.error(u'invalid audio quality specified')
7851b379
PH
381 if opts.recodevideo is not None:
382 if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
383 parser.error(u'invalid video recode format specified')
59ae15a5 384
5cb9c312
PH
385 if sys.version_info < (3,):
386 # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
0be41ec2
PH
387 if opts.outtmpl is not None:
388 opts.outtmpl = opts.outtmpl.decode(preferredencoding())
5cb9c312
PH
389 outtmpl =((opts.outtmpl is not None and opts.outtmpl)
390 or (opts.format == '-1' and opts.usetitle and u'%(title)s-%(id)s-%(format)s.%(ext)s')
391 or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
392 or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
393 or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
394 or (opts.useid and u'%(id)s.%(ext)s')
395 or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
396 or u'%(id)s.%(ext)s')
59ae15a5
PH
397 # File downloader
398 fd = FileDownloader({
399 'usenetrc': opts.usenetrc,
400 'username': opts.username,
401 'password': opts.password,
402 'quiet': (opts.quiet or opts.geturl or opts.gettitle or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat),
403 'forceurl': opts.geturl,
404 'forcetitle': opts.gettitle,
405 'forcethumbnail': opts.getthumbnail,
406 'forcedescription': opts.getdescription,
407 'forcefilename': opts.getfilename,
408 'forceformat': opts.getformat,
409 'simulate': opts.simulate,
410 '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),
411 'format': opts.format,
412 'format_limit': opts.format_limit,
413 'listformats': opts.listformats,
5cb9c312 414 'outtmpl': outtmpl,
59ae15a5
PH
415 'restrictfilenames': opts.restrictfilenames,
416 'ignoreerrors': opts.ignoreerrors,
417 'ratelimit': opts.ratelimit,
418 'nooverwrites': opts.nooverwrites,
419 'retries': opts.retries,
420 'buffersize': opts.buffersize,
421 'noresizebuffer': opts.noresizebuffer,
422 'continuedl': opts.continue_dl,
423 'noprogress': opts.noprogress,
424 'playliststart': opts.playliststart,
425 'playlistend': opts.playlistend,
426 'logtostderr': opts.outtmpl == '-',
427 'consoletitle': opts.consoletitle,
428 'nopart': opts.nopart,
429 'updatetime': opts.updatetime,
430 'writedescription': opts.writedescription,
431 'writeinfojson': opts.writeinfojson,
432 'writesubtitles': opts.writesubtitles,
433 'subtitleslang': opts.subtitleslang,
434 'matchtitle': opts.matchtitle,
435 'rejecttitle': opts.rejecttitle,
436 'max_downloads': opts.max_downloads,
437 'prefer_free_formats': opts.prefer_free_formats,
438 'verbose': opts.verbose,
8d5d3a5d 439 'test': opts.test,
7851b379 440 'keepvideo': opts.keepvideo,
59ae15a5
PH
441 })
442
443 if opts.verbose:
f427df17 444 fd.to_screen(u'[debug] youtube-dl version ' + __version__)
4e38899e 445 try:
f427df17
FV
446 sp = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
447 cwd=os.path.dirname(os.path.abspath(__file__)))
4e38899e
FV
448 out, err = sp.communicate()
449 out = out.decode().strip()
450 if re.match('[0-9a-f]+', out):
451 fd.to_screen(u'[debug] Git HEAD: ' + out)
452 except:
453 pass
454 fd.to_screen(u'[debug] Python version %s - %s' %(platform.python_version(), platform.platform()))
59ae15a5
PH
455 fd.to_screen(u'[debug] Proxy map: ' + str(proxy_handler.proxies))
456
457 for extractor in extractors:
458 fd.add_info_extractor(extractor)
459
460 # PostProcessors
461 if opts.extractaudio:
7851b379
PH
462 fd.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
463 if opts.recodevideo:
464 fd.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
59ae15a5 465
67353612
PH
466 # Update version
467 if opts.update_self:
468 update_self(fd.to_screen, opts.verbose, sys.argv[0])
469
59ae15a5
PH
470 # Maybe do nothing
471 if len(all_urls) < 1:
472 if not opts.update_self:
473 parser.error(u'you must provide at least one URL')
474 else:
475 sys.exit()
476
477 try:
478 retcode = fd.download(all_urls)
479 except MaxDownloadsReached:
480 fd.to_screen(u'--max-download limit reached, aborting.')
481 retcode = 101
482
483 # Dump cookie jar if requested
484 if opts.cookiefile is not None:
485 try:
486 jar.save()
487 except (IOError, OSError) as err:
488 sys.exit(u'ERROR: unable to save cookie jar')
489
490 sys.exit(retcode)
235b3ba4
PH
491
492def main():
59ae15a5
PH
493 try:
494 _real_main()
495 except DownloadError:
496 sys.exit(1)
497 except SameFileError:
498 sys.exit(u'ERROR: fixed output name but more than one file to download')
499 except KeyboardInterrupt:
500 sys.exit(u'\nERROR: Interrupted by user')