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