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