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