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