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