Merge remote-tracking branch 'origin/master'
[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     'Christian Albrecht',
20     'Dave Vasilevsky',
21     'Jaime Marquínez Ferrándiz',
22     'Jeff Crouse',
23     'Osama Khalid',
24     'Michael Walter',
25     'M. Yasoob Ullah Khalid',
26     'Julien Fraichard',
27     'Johny Mo Swag',
28     'Axel Noack',
29     'Albert Kim',
30     'Pierre Rudloff',
31     'Huarong Huo',
32     'Ismael Mejía',
33     'Steffan \'Ruirize\' James',
34     'Andras Elso',
35     'Jelle van der Waa',
36     'Marcin Cieślak',
37     'Anton Larionov',
38     'Takuya Tsuchida',
39     'Sergey M.',
40     'Michael Orlitzky',
41     'Chris Gahan',
42     'Saimadhav Heblikar',
43     'Mike Col',
44     'Oleg Prutz',
45     'pulpe',
46     'Andreas Schmitz',
47     'Michael Kaiser',
48     'Niklas Laxström',
49     'David Triendl',
50     'Anthony Weems',
51     'David Wagner',
52     'Juan C. Olivares',
53     'Mattias Harrysson',
54     'phaer',
55     'Sainyam Kapoor',
56     'Nicolas Évrard',
57     'Jason Normore',
58     'Hoje Lee',
59     'Adam Thalhammer',
60     'Georg Jähnig',
61     'Ralf Haring',
62     'Koki Takahashi',
63     'Ariset Llerena',
64     'Adam Malcontenti-Wilson',
65     'Tobias Bell',
66     'Naglis Jonaitis',
67     'Charles Chen',
68     'Hassaan Ali',
69     'Dobrosław Żybort',
70     'David Fabijan',
71     'Sebastian Haas',
72     'Alexander Kirk',
73     'Erik Johnson',
74     'Keith Beckman',
75     'Ole Ernst',
76     'Aaron McDaniel (mcd1992)',
77     'Magnus Kolstad',
78     'Hari Padmanaban',
79     'Carlos Ramos',
80 )
81
82 __license__ = 'Public Domain'
83
84 import codecs
85 import io
86 import os
87 import random
88 import sys
89
90
91 from .options import (
92     parseOpts,
93 )
94 from .utils import (
95     compat_getpass,
96     compat_print,
97     DateRange,
98     DEFAULT_OUTTMPL,
99     decodeOption,
100     DownloadError,
101     MaxDownloadsReached,
102     preferredencoding,
103     read_batch_urls,
104     SameFileError,
105     setproctitle,
106     std_headers,
107     write_string,
108 )
109 from .update import update_self
110 from .downloader import (
111     FileDownloader,
112 )
113 from .extractor import gen_extractors
114 from .YoutubeDL import YoutubeDL
115 from .postprocessor import (
116     AtomicParsleyPP,
117     FFmpegAudioFixPP,
118     FFmpegMetadataPP,
119     FFmpegVideoConvertor,
120     FFmpegExtractAudioPP,
121     FFmpegEmbedSubtitlePP,
122     XAttrMetadataPP,
123     ExecAfterDownloadPP,
124 )
125
126
127 def _real_main(argv=None):
128     # Compatibility fixes for Windows
129     if sys.platform == 'win32':
130         # https://github.com/rg3/youtube-dl/issues/820
131         codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)
132
133     setproctitle(u'youtube-dl')
134
135     parser, opts, args = parseOpts(argv)
136
137     # Set user agent
138     if opts.user_agent is not None:
139         std_headers['User-Agent'] = opts.user_agent
140
141     # Set referer
142     if opts.referer is not None:
143         std_headers['Referer'] = opts.referer
144
145     # Custom HTTP headers
146     if opts.headers is not None:
147         for h in opts.headers:
148             if h.find(':', 1) < 0:
149                 parser.error(u'wrong header formatting, it should be key:value, not "%s"'%h)
150             key, value = h.split(':', 2)
151             if opts.verbose:
152                 write_string(u'[debug] Adding header from command line option %s:%s\n'%(key, value))
153             std_headers[key] = value
154
155     # Dump user agent
156     if opts.dump_user_agent:
157         compat_print(std_headers['User-Agent'])
158         sys.exit(0)
159
160     # Batch file verification
161     batch_urls = []
162     if opts.batchfile is not None:
163         try:
164             if opts.batchfile == '-':
165                 batchfd = sys.stdin
166             else:
167                 batchfd = io.open(opts.batchfile, 'r', encoding='utf-8', errors='ignore')
168             batch_urls = read_batch_urls(batchfd)
169             if opts.verbose:
170                 write_string(u'[debug] Batch file urls: ' + repr(batch_urls) + u'\n')
171         except IOError:
172             sys.exit(u'ERROR: batch file could not be read')
173     all_urls = batch_urls + args
174     all_urls = [url.strip() for url in all_urls]
175     _enc = preferredencoding()
176     all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls]
177
178     extractors = gen_extractors()
179
180     if opts.list_extractors:
181         for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()):
182             compat_print(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie._WORKING else ''))
183             matchedUrls = [url for url in all_urls if ie.suitable(url)]
184             for mu in matchedUrls:
185                 compat_print(u'  ' + mu)
186         sys.exit(0)
187     if opts.list_extractor_descriptions:
188         for ie in sorted(extractors, key=lambda ie: ie.IE_NAME.lower()):
189             if not ie._WORKING:
190                 continue
191             desc = getattr(ie, 'IE_DESC', ie.IE_NAME)
192             if desc is False:
193                 continue
194             if hasattr(ie, 'SEARCH_KEY'):
195                 _SEARCHES = (u'cute kittens', u'slithering pythons', u'falling cat', u'angry poodle', u'purple fish', u'running tortoise', u'sleeping bunny')
196                 _COUNTS = (u'', u'5', u'10', u'all')
197                 desc += u' (Example: "%s%s:%s" )' % (ie.SEARCH_KEY, random.choice(_COUNTS), random.choice(_SEARCHES))
198             compat_print(desc)
199         sys.exit(0)
200
201
202     # Conflicting, missing and erroneous options
203     if opts.usenetrc and (opts.username is not None or opts.password is not None):
204         parser.error(u'using .netrc conflicts with giving username/password')
205     if opts.password is not None and opts.username is None:
206         parser.error(u'account username missing\n')
207     if opts.outtmpl is not None and (opts.usetitle or opts.autonumber or opts.useid):
208         parser.error(u'using output template conflicts with using title, video ID or auto number')
209     if opts.usetitle and opts.useid:
210         parser.error(u'using title conflicts with using video ID')
211     if opts.username is not None and opts.password is None:
212         opts.password = compat_getpass(u'Type account password and press [Return]: ')
213     if opts.ratelimit is not None:
214         numeric_limit = FileDownloader.parse_bytes(opts.ratelimit)
215         if numeric_limit is None:
216             parser.error(u'invalid rate limit specified')
217         opts.ratelimit = numeric_limit
218     if opts.min_filesize is not None:
219         numeric_limit = FileDownloader.parse_bytes(opts.min_filesize)
220         if numeric_limit is None:
221             parser.error(u'invalid min_filesize specified')
222         opts.min_filesize = numeric_limit
223     if opts.max_filesize is not None:
224         numeric_limit = FileDownloader.parse_bytes(opts.max_filesize)
225         if numeric_limit is None:
226             parser.error(u'invalid max_filesize specified')
227         opts.max_filesize = numeric_limit
228     if opts.retries is not None:
229         try:
230             opts.retries = int(opts.retries)
231         except (TypeError, ValueError):
232             parser.error(u'invalid retry count specified')
233     if opts.buffersize is not None:
234         numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize)
235         if numeric_buffersize is None:
236             parser.error(u'invalid buffer size specified')
237         opts.buffersize = numeric_buffersize
238     if opts.playliststart <= 0:
239         raise ValueError(u'Playlist start must be positive')
240     if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart:
241         raise ValueError(u'Playlist end must be greater than playlist start')
242     if opts.extractaudio:
243         if opts.audioformat not in ['best', 'aac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav']:
244             parser.error(u'invalid audio format specified')
245     if opts.audioquality:
246         opts.audioquality = opts.audioquality.strip('k').strip('K')
247         if not opts.audioquality.isdigit():
248             parser.error(u'invalid audio quality specified')
249     if opts.recodevideo is not None:
250         if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg', 'mkv']:
251             parser.error(u'invalid video recode format specified')
252     if opts.date is not None:
253         date = DateRange.day(opts.date)
254     else:
255         date = DateRange(opts.dateafter, opts.datebefore)
256     if opts.default_search not in ('auto', 'auto_warning', 'error', 'fixup_error', None) and ':' not in opts.default_search:
257         parser.error(u'--default-search invalid; did you forget a colon (:) at the end?')
258
259     # Do not download videos when there are audio-only formats
260     if opts.extractaudio and not opts.keepvideo and opts.format is None:
261         opts.format = 'bestaudio/best'
262
263     # --all-sub automatically sets --write-sub if --write-auto-sub is not given
264     # this was the old behaviour if only --all-sub was given.
265     if opts.allsubtitles and (opts.writeautomaticsub == False):
266         opts.writesubtitles = True
267
268     if sys.version_info < (3,):
269         # In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
270         if opts.outtmpl is not None:
271             opts.outtmpl = opts.outtmpl.decode(preferredencoding())
272     outtmpl =((opts.outtmpl is not None and opts.outtmpl)
273             or (opts.format == '-1' and opts.usetitle and u'%(title)s-%(id)s-%(format)s.%(ext)s')
274             or (opts.format == '-1' and u'%(id)s-%(format)s.%(ext)s')
275             or (opts.usetitle and opts.autonumber and u'%(autonumber)s-%(title)s-%(id)s.%(ext)s')
276             or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
277             or (opts.useid and u'%(id)s.%(ext)s')
278             or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
279             or DEFAULT_OUTTMPL)
280     if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
281         parser.error(u'Cannot download a video and extract audio into the same'
282                      u' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
283                      u' template'.format(outtmpl))
284
285     any_printing = opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson
286     download_archive_fn = os.path.expanduser(opts.download_archive) if opts.download_archive is not None else opts.download_archive
287
288     ydl_opts = {
289         'usenetrc': opts.usenetrc,
290         'username': opts.username,
291         'password': opts.password,
292         'twofactor': opts.twofactor,
293         'videopassword': opts.videopassword,
294         'quiet': (opts.quiet or any_printing),
295         'no_warnings': opts.no_warnings,
296         'forceurl': opts.geturl,
297         'forcetitle': opts.gettitle,
298         'forceid': opts.getid,
299         'forcethumbnail': opts.getthumbnail,
300         'forcedescription': opts.getdescription,
301         'forceduration': opts.getduration,
302         'forcefilename': opts.getfilename,
303         'forceformat': opts.getformat,
304         'forcejson': opts.dumpjson,
305         'simulate': opts.simulate,
306         'skip_download': (opts.skip_download or opts.simulate or any_printing),
307         'format': opts.format,
308         'format_limit': opts.format_limit,
309         'listformats': opts.listformats,
310         'outtmpl': outtmpl,
311         'autonumber_size': opts.autonumber_size,
312         'restrictfilenames': opts.restrictfilenames,
313         'ignoreerrors': opts.ignoreerrors,
314         'ratelimit': opts.ratelimit,
315         'nooverwrites': opts.nooverwrites,
316         'retries': opts.retries,
317         'buffersize': opts.buffersize,
318         'noresizebuffer': opts.noresizebuffer,
319         'continuedl': opts.continue_dl,
320         'noprogress': opts.noprogress,
321         'progress_with_newline': opts.progress_with_newline,
322         'playliststart': opts.playliststart,
323         'playlistend': opts.playlistend,
324         'noplaylist': opts.noplaylist,
325         'logtostderr': opts.outtmpl == '-',
326         'consoletitle': opts.consoletitle,
327         'nopart': opts.nopart,
328         'updatetime': opts.updatetime,
329         'writedescription': opts.writedescription,
330         'writeannotations': opts.writeannotations,
331         'writeinfojson': opts.writeinfojson,
332         'writethumbnail': opts.writethumbnail,
333         'writesubtitles': opts.writesubtitles,
334         'writeautomaticsub': opts.writeautomaticsub,
335         'allsubtitles': opts.allsubtitles,
336         'listsubtitles': opts.listsubtitles,
337         'subtitlesformat': opts.subtitlesformat,
338         'subtitleslangs': opts.subtitleslangs,
339         'matchtitle': decodeOption(opts.matchtitle),
340         'rejecttitle': decodeOption(opts.rejecttitle),
341         'max_downloads': opts.max_downloads,
342         'prefer_free_formats': opts.prefer_free_formats,
343         'verbose': opts.verbose,
344         'dump_intermediate_pages': opts.dump_intermediate_pages,
345         'write_pages': opts.write_pages,
346         'test': opts.test,
347         'keepvideo': opts.keepvideo,
348         'min_filesize': opts.min_filesize,
349         'max_filesize': opts.max_filesize,
350         'min_views': opts.min_views,
351         'max_views': opts.max_views,
352         'daterange': date,
353         'cachedir': opts.cachedir,
354         'youtube_print_sig_code': opts.youtube_print_sig_code,
355         'age_limit': opts.age_limit,
356         'download_archive': download_archive_fn,
357         'cookiefile': opts.cookiefile,
358         'nocheckcertificate': opts.no_check_certificate,
359         'prefer_insecure': opts.prefer_insecure,
360         'proxy': opts.proxy,
361         'socket_timeout': opts.socket_timeout,
362         'bidi_workaround': opts.bidi_workaround,
363         'debug_printtraffic': opts.debug_printtraffic,
364         'prefer_ffmpeg': opts.prefer_ffmpeg,
365         'include_ads': opts.include_ads,
366         'default_search': opts.default_search,
367         'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
368         'encoding': opts.encoding,
369         'exec_cmd': opts.exec_cmd,
370     }
371
372     with YoutubeDL(ydl_opts) as ydl:
373         ydl.print_debug_header()
374         ydl.add_default_info_extractors()
375
376         # PostProcessors
377         # Add the metadata pp first, the other pps will copy it
378         if opts.addmetadata:
379             ydl.add_post_processor(FFmpegMetadataPP())
380         if opts.extractaudio:
381             ydl.add_post_processor(FFmpegExtractAudioPP(preferredcodec=opts.audioformat, preferredquality=opts.audioquality, nopostoverwrites=opts.nopostoverwrites))
382         if opts.recodevideo:
383             ydl.add_post_processor(FFmpegVideoConvertor(preferedformat=opts.recodevideo))
384         if opts.embedsubtitles:
385             ydl.add_post_processor(FFmpegEmbedSubtitlePP(subtitlesformat=opts.subtitlesformat))
386         if opts.xattrs:
387             ydl.add_post_processor(XAttrMetadataPP())
388         if opts.embedthumbnail:
389             if not opts.addmetadata:
390                 ydl.add_post_processor(FFmpegAudioFixPP())
391             ydl.add_post_processor(AtomicParsleyPP())
392
393
394         # Please keep ExecAfterDownload towards the bottom as it allows the user to modify the final file in any way.
395         # So if the user is able to remove the file before your postprocessor runs it might cause a few problems.
396         if opts.exec_cmd:
397             ydl.add_post_processor(ExecAfterDownloadPP(
398                 verboseOutput=opts.verbose, exec_cmd=opts.exec_cmd))
399
400         # Update version
401         if opts.update_self:
402             update_self(ydl.to_screen, opts.verbose)
403
404         # Remove cache dir
405         if opts.rm_cachedir:
406             ydl.cache.remove()
407
408         # Maybe do nothing
409         if (len(all_urls) < 1) and (opts.load_info_filename is None):
410             if not (opts.update_self or opts.rm_cachedir):
411                 parser.error(u'you must provide at least one URL')
412             else:
413                 sys.exit()
414
415         try:
416             if opts.load_info_filename is not None:
417                 retcode = ydl.download_with_info_file(opts.load_info_filename)
418             else:
419                 retcode = ydl.download(all_urls)
420         except MaxDownloadsReached:
421             ydl.to_screen(u'--max-download limit reached, aborting.')
422             retcode = 101
423
424     sys.exit(retcode)
425
426
427 def main(argv=None):
428     try:
429         _real_main(argv)
430     except DownloadError:
431         sys.exit(1)
432     except SameFileError:
433         sys.exit(u'ERROR: fixed output name but more than one file to download')
434     except KeyboardInterrupt:
435         sys.exit(u'\nERROR: Interrupted by user')