]> jfr.im git - yt-dlp.git/blame - youtube_dl/YoutubeDL.py
Merge remote-tracking branch 'jaimeMF/opus-fix'
[yt-dlp.git] / youtube_dl / YoutubeDL.py
CommitLineData
8222d8de
JMF
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4from __future__ import absolute_import
5
c1c9a79c 6import errno
8222d8de
JMF
7import io
8import os
9import re
10import shutil
11import socket
12import sys
13import time
14import traceback
15
16from .utils import *
023fa8c4 17from .extractor import get_info_extractor, gen_extractors
8222d8de
JMF
18from .FileDownloader import FileDownloader
19
20
21class YoutubeDL(object):
22 """YoutubeDL class.
23
24 YoutubeDL objects are the ones responsible of downloading the
25 actual video file and writing it to disk if the user has requested
26 it, among some other tasks. In most cases there should be one per
27 program. As, given a video URL, the downloader doesn't know how to
28 extract all the needed information, task that InfoExtractors do, it
29 has to pass the URL to one of them.
30
31 For this, YoutubeDL objects have a method that allows
32 InfoExtractors to be registered in a given order. When it is passed
33 a URL, the YoutubeDL object handles it to the first InfoExtractor it
34 finds that reports being able to handle it. The InfoExtractor extracts
35 all the information about the video or videos the URL refers to, and
36 YoutubeDL process the extracted information, possibly using a File
37 Downloader to download the video.
38
39 YoutubeDL objects accept a lot of parameters. In order not to saturate
40 the object constructor with arguments, it receives a dictionary of
41 options instead. These options are available through the params
42 attribute for the InfoExtractors to use. The YoutubeDL also
43 registers itself as the downloader in charge for the InfoExtractors
44 that are added to it, so this is a "mutual registration".
45
46 Available options:
47
48 username: Username for authentication purposes.
49 password: Password for authentication purposes.
c6c19746 50 videopassword: Password for acces a video.
8222d8de
JMF
51 usenetrc: Use netrc for authentication instead.
52 verbose: Print additional info to stdout.
53 quiet: Do not print messages to stdout.
54 forceurl: Force printing final URL.
55 forcetitle: Force printing title.
56 forceid: Force printing ID.
57 forcethumbnail: Force printing thumbnail URL.
58 forcedescription: Force printing description.
59 forcefilename: Force printing final filename.
60 simulate: Do not download the video files.
61 format: Video format code.
62 format_limit: Highest quality format to try.
63 outtmpl: Template for output names.
64 restrictfilenames: Do not allow "&" and spaces in file names
65 ignoreerrors: Do not stop on download errors.
66 nooverwrites: Prevent overwriting files.
67 playliststart: Playlist item to start at.
68 playlistend: Playlist item to end at.
69 matchtitle: Download only matching titles.
70 rejecttitle: Reject downloads for matching titles.
71 logtostderr: Log messages to stderr instead of stdout.
72 writedescription: Write the video description to a .description file
73 writeinfojson: Write the video description to a .info.json file
74 writethumbnail: Write the thumbnail image to a file
75 writesubtitles: Write the video subtitles to a file
b004821f 76 writeautomaticsub: Write the automatic subtitles to a file
8222d8de 77 allsubtitles: Downloads all the subtitles of the video
0b7f3118 78 (requires writesubtitles or writeautomaticsub)
8222d8de 79 listsubtitles: Lists all available subtitles for the video
b98a6b2f 80 subtitlesformat: Subtitle format [srt/sbv/vtt] (default=srt)
aa6a10c4 81 subtitleslangs: List of languages of the subtitles to download
8222d8de
JMF
82 keepvideo: Keep the video file after post-processing
83 daterange: A DateRange object, download only if the upload_date is in the range.
84 skip_download: Skip the actual download of the video file
c35f9e72 85 cachedir: Location of the cache files in the filesystem.
c3c88a26 86 None to disable filesystem cache.
47192f92 87 noplaylist: Download single video instead of a playlist if in doubt.
8dbe9899
PH
88 age_limit: An integer representing the user's age in years.
89 Unsuitable videos for the given age are skipped.
c1c9a79c
PH
90 downloadarchive: File name of a file where all downloads are recorded.
91 Videos already present in the file are not downloaded
92 again.
8222d8de
JMF
93
94 The following parameters are not used by YoutubeDL itself, they are used by
95 the FileDownloader:
96 nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
97 noresizebuffer, retries, continuedl, noprogress, consoletitle
98 """
99
100 params = None
101 _ies = []
102 _pps = []
103 _download_retcode = None
104 _num_downloads = None
105 _screen_file = None
106
107 def __init__(self, params):
108 """Create a FileDownloader object with the given options."""
109 self._ies = []
56c73665 110 self._ies_instances = {}
8222d8de
JMF
111 self._pps = []
112 self._progress_hooks = []
113 self._download_retcode = 0
114 self._num_downloads = 0
115 self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
34308b30
PH
116
117 if (sys.version_info >= (3,) and sys.platform != 'win32' and
118 sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968']
119 and not params['restrictfilenames']):
120 # On Python 3, the Unicode filesystem API will throw errors (#1474)
121 self.report_warning(
1d368c75 122 u'Assuming --restrict-filenames since file system encoding '
34308b30
PH
123 u'cannot encode all charactes. '
124 u'Set the LC_ALL environment variable to fix this.')
125 params['restrictfilenames'] = True
126
8222d8de
JMF
127 self.params = params
128 self.fd = FileDownloader(self, self.params)
129
130 if '%(stitle)s' in self.params['outtmpl']:
131 self.report_warning(u'%(stitle)s is deprecated. Use the %(title)s and the --restrict-filenames flag(which also secures %(uploader)s et al) instead.')
132
133 def add_info_extractor(self, ie):
134 """Add an InfoExtractor object to the end of the list."""
135 self._ies.append(ie)
56c73665 136 self._ies_instances[ie.ie_key()] = ie
8222d8de
JMF
137 ie.set_downloader(self)
138
56c73665
JMF
139 def get_info_extractor(self, ie_key):
140 """
141 Get an instance of an IE with name ie_key, it will try to get one from
142 the _ies list, if there's no instance it will create a new one and add
143 it to the extractor list.
144 """
145 ie = self._ies_instances.get(ie_key)
146 if ie is None:
147 ie = get_info_extractor(ie_key)()
148 self.add_info_extractor(ie)
149 return ie
150
023fa8c4
JMF
151 def add_default_info_extractors(self):
152 """
153 Add the InfoExtractors returned by gen_extractors to the end of the list
154 """
155 for ie in gen_extractors():
156 self.add_info_extractor(ie)
157
8222d8de
JMF
158 def add_post_processor(self, pp):
159 """Add a PostProcessor object to the end of the chain."""
160 self._pps.append(pp)
161 pp.set_downloader(self)
162
163 def to_screen(self, message, skip_eol=False):
164 """Print message to stdout if not in quiet mode."""
8222d8de
JMF
165 if not self.params.get('quiet', False):
166 terminator = [u'\n', u''][skip_eol]
167 output = message + terminator
7459e3a2 168 write_string(output, self._screen_file)
8222d8de
JMF
169
170 def to_stderr(self, message):
171 """Print message to stderr."""
172 assert type(message) == type(u'')
173 output = message + u'\n'
174 if 'b' in getattr(self._screen_file, 'mode', '') or sys.version_info[0] < 3: # Python 2 lies about the mode of sys.stdout/sys.stderr
175 output = output.encode(preferredencoding())
176 sys.stderr.write(output)
177
178 def fixed_template(self):
179 """Checks if the output template is fixed."""
180 return (re.search(u'(?u)%\\(.+?\\)s', self.params['outtmpl']) is None)
181
182 def trouble(self, message=None, tb=None):
183 """Determine action to take when a download problem appears.
184
185 Depending on if the downloader has been configured to ignore
186 download errors or not, this method may throw an exception or
187 not when errors are found, after printing the message.
188
189 tb, if given, is additional traceback information.
190 """
191 if message is not None:
192 self.to_stderr(message)
193 if self.params.get('verbose'):
194 if tb is None:
195 if sys.exc_info()[0]: # if .trouble has been called from an except block
196 tb = u''
197 if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
198 tb += u''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
199 tb += compat_str(traceback.format_exc())
200 else:
201 tb_data = traceback.format_list(traceback.extract_stack())
202 tb = u''.join(tb_data)
203 self.to_stderr(tb)
204 if not self.params.get('ignoreerrors', False):
205 if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
206 exc_info = sys.exc_info()[1].exc_info
207 else:
208 exc_info = sys.exc_info()
209 raise DownloadError(message, exc_info)
210 self._download_retcode = 1
211
212 def report_warning(self, message):
213 '''
214 Print the message to stderr, it will be prefixed with 'WARNING:'
215 If stderr is a tty file the 'WARNING:' will be colored
216 '''
217 if sys.stderr.isatty() and os.name != 'nt':
218 _msg_header=u'\033[0;33mWARNING:\033[0m'
219 else:
220 _msg_header=u'WARNING:'
221 warning_message=u'%s %s' % (_msg_header,message)
222 self.to_stderr(warning_message)
223
224 def report_error(self, message, tb=None):
225 '''
226 Do the same as trouble, but prefixes the message with 'ERROR:', colored
227 in red if stderr is a tty file.
228 '''
229 if sys.stderr.isatty() and os.name != 'nt':
230 _msg_header = u'\033[0;31mERROR:\033[0m'
231 else:
232 _msg_header = u'ERROR:'
233 error_message = u'%s %s' % (_msg_header, message)
234 self.trouble(error_message, tb)
235
236 def slow_down(self, start_time, byte_counter):
237 """Sleep if the download speed is over the rate limit."""
238 rate_limit = self.params.get('ratelimit', None)
239 if rate_limit is None or byte_counter == 0:
240 return
241 now = time.time()
242 elapsed = now - start_time
243 if elapsed <= 0.0:
244 return
245 speed = float(byte_counter) / elapsed
246 if speed > rate_limit:
247 time.sleep((byte_counter - rate_limit * (now - start_time)) / rate_limit)
248
249 def report_writedescription(self, descfn):
250 """ Report that the description file is being written """
251 self.to_screen(u'[info] Writing video description to: ' + descfn)
252
253 def report_writesubtitles(self, sub_filename):
254 """ Report that the subtitles file is being written """
255 self.to_screen(u'[info] Writing video subtitles to: ' + sub_filename)
256
257 def report_writeinfojson(self, infofn):
258 """ Report that the metadata file has been written """
259 self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn)
260
261 def report_file_already_downloaded(self, file_name):
262 """Report file has already been fully downloaded."""
263 try:
264 self.to_screen(u'[download] %s has already been downloaded' % file_name)
265 except (UnicodeEncodeError) as err:
266 self.to_screen(u'[download] The file has already been downloaded')
267
268 def increment_downloads(self):
269 """Increment the ordinal that assigns a number to each file."""
270 self._num_downloads += 1
271
272 def prepare_filename(self, info_dict):
273 """Generate the output filename."""
274 try:
275 template_dict = dict(info_dict)
276
277 template_dict['epoch'] = int(time.time())
278 autonumber_size = self.params.get('autonumber_size')
279 if autonumber_size is None:
280 autonumber_size = 5
281 autonumber_templ = u'%0' + str(autonumber_size) + u'd'
282 template_dict['autonumber'] = autonumber_templ % self._num_downloads
283 if template_dict['playlist_index'] is not None:
284 template_dict['playlist_index'] = u'%05d' % template_dict['playlist_index']
285
286 sanitize = lambda k,v: sanitize_filename(
287 u'NA' if v is None else compat_str(v),
288 restricted=self.params.get('restrictfilenames'),
289 is_id=(k==u'id'))
290 template_dict = dict((k, sanitize(k, v)) for k,v in template_dict.items())
291
292 filename = self.params['outtmpl'] % template_dict
293 return filename
294 except KeyError as err:
295 self.report_error(u'Erroneous output template')
296 return None
297 except ValueError as err:
4efba05c 298 self.report_error(u'Error in output template: ' + str(err) + u' (encoding: ' + repr(preferredencoding()) + ')')
8222d8de
JMF
299 return None
300
301 def _match_entry(self, info_dict):
302 """ Returns None iff the file should be downloaded """
303
304 title = info_dict['title']
305 matchtitle = self.params.get('matchtitle', False)
306 if matchtitle:
307 if not re.search(matchtitle, title, re.IGNORECASE):
308 return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
309 rejecttitle = self.params.get('rejecttitle', False)
310 if rejecttitle:
311 if re.search(rejecttitle, title, re.IGNORECASE):
312 return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
313 date = info_dict.get('upload_date', None)
314 if date is not None:
315 dateRange = self.params.get('daterange', DateRange())
316 if date not in dateRange:
317 return u'[download] %s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
8dbe9899
PH
318 age_limit = self.params.get('age_limit')
319 if age_limit is not None:
cfadd183 320 if age_limit < info_dict.get('age_limit', 0):
8dbe9899 321 return u'Skipping "' + title + '" because it is age restricted'
c1c9a79c 322 if self.in_download_archive(info_dict):
ee6c9f95 323 return (u'%(title)s has already been recorded in archive'
c1c9a79c 324 % info_dict)
8222d8de
JMF
325 return None
326
327 def extract_info(self, url, download=True, ie_key=None, extra_info={}):
328 '''
329 Returns a list with a dictionary for each video we find.
330 If 'download', also downloads the videos.
331 extra_info is a dict containing the extra values to add to each result
332 '''
333
334 if ie_key:
56c73665 335 ies = [self.get_info_extractor(ie_key)]
8222d8de
JMF
336 else:
337 ies = self._ies
338
339 for ie in ies:
340 if not ie.suitable(url):
341 continue
342
343 if not ie.working():
344 self.report_warning(u'The program functionality for this site has been marked as broken, '
345 u'and will probably not work.')
346
347 try:
348 ie_result = ie.extract(url)
349 if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
350 break
351 if isinstance(ie_result, list):
352 # Backwards compatibility: old IE result format
353 for result in ie_result:
354 result.update(extra_info)
355 ie_result = {
356 '_type': 'compat_list',
357 'entries': ie_result,
358 }
359 else:
360 ie_result.update(extra_info)
361 if 'extractor' not in ie_result:
362 ie_result['extractor'] = ie.IE_NAME
363 return self.process_ie_result(ie_result, download=download)
364 except ExtractorError as de: # An error we somewhat expected
365 self.report_error(compat_str(de), de.format_traceback())
366 break
367 except Exception as e:
368 if self.params.get('ignoreerrors', False):
369 self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
370 break
371 else:
372 raise
373 else:
374 self.report_error(u'no suitable InfoExtractor: %s' % url)
375
376 def process_ie_result(self, ie_result, download=True, extra_info={}):
377 """
378 Take the result of the ie(may be modified) and resolve all unresolved
379 references (URLs, playlist items).
380
381 It will also download the videos if 'download'.
382 Returns the resolved ie_result.
383 """
384
385 result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
386 if result_type == 'video':
a4311547 387 ie_result.update(extra_info)
8222d8de
JMF
388 if 'playlist' not in ie_result:
389 # It isn't part of a playlist
390 ie_result['playlist'] = None
391 ie_result['playlist_index'] = None
392 if download:
393 self.process_info(ie_result)
394 return ie_result
395 elif result_type == 'url':
396 # We have to add extra_info to the results because it may be
397 # contained in a playlist
398 return self.extract_info(ie_result['url'],
399 download,
400 ie_key=ie_result.get('ie_key'),
401 extra_info=extra_info)
402 elif result_type == 'playlist':
403 # We process each entry in the playlist
404 playlist = ie_result.get('title', None) or ie_result.get('id', None)
405 self.to_screen(u'[download] Downloading playlist: %s' % playlist)
406
407 playlist_results = []
408
409 n_all_entries = len(ie_result['entries'])
410 playliststart = self.params.get('playliststart', 1) - 1
411 playlistend = self.params.get('playlistend', -1)
412
413 if playlistend == -1:
414 entries = ie_result['entries'][playliststart:]
415 else:
416 entries = ie_result['entries'][playliststart:playlistend]
417
418 n_entries = len(entries)
419
420 self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
421 (ie_result['extractor'], playlist, n_all_entries, n_entries))
422
423 for i,entry in enumerate(entries,1):
424 self.to_screen(u'[download] Downloading video #%s of %s' %(i, n_entries))
425 extra = {
426 'playlist': playlist,
427 'playlist_index': i + playliststart,
428 }
429 if not 'extractor' in entry:
430 # We set the extractor, if it's an url it will be set then to
431 # the new extractor, but if it's already a video we must make
432 # sure it's present: see issue #877
433 entry['extractor'] = ie_result['extractor']
434 entry_result = self.process_ie_result(entry,
435 download=download,
436 extra_info=extra)
437 playlist_results.append(entry_result)
438 ie_result['entries'] = playlist_results
439 return ie_result
440 elif result_type == 'compat_list':
441 def _fixup(r):
442 r.setdefault('extractor', ie_result['extractor'])
443 return r
444 ie_result['entries'] = [
445 self.process_ie_result(_fixup(r), download=download)
446 for r in ie_result['entries']
447 ]
448 return ie_result
449 else:
450 raise Exception('Invalid result type: %s' % result_type)
451
452 def process_info(self, info_dict):
453 """Process a single resolved IE result."""
454
455 assert info_dict.get('_type', 'video') == 'video'
456 #We increment the download the download count here to match the previous behaviour.
457 self.increment_downloads()
458
459 info_dict['fulltitle'] = info_dict['title']
460 if len(info_dict['title']) > 200:
461 info_dict['title'] = info_dict['title'][:197] + u'...'
462
463 # Keep for backwards compatibility
464 info_dict['stitle'] = info_dict['title']
465
466 if not 'format' in info_dict:
467 info_dict['format'] = info_dict['ext']
468
469 reason = self._match_entry(info_dict)
470 if reason is not None:
471 self.to_screen(u'[download] ' + reason)
472 return
473
474 max_downloads = self.params.get('max_downloads')
475 if max_downloads is not None:
476 if self._num_downloads > int(max_downloads):
477 raise MaxDownloadsReached()
478
479 filename = self.prepare_filename(info_dict)
480
481 # Forced printings
482 if self.params.get('forcetitle', False):
483 compat_print(info_dict['title'])
484 if self.params.get('forceid', False):
485 compat_print(info_dict['id'])
486 if self.params.get('forceurl', False):
edde6c56
PH
487 # For RTMP URLs, also include the playpath
488 compat_print(info_dict['url'] + info_dict.get('play_path', u''))
8222d8de
JMF
489 if self.params.get('forcethumbnail', False) and 'thumbnail' in info_dict:
490 compat_print(info_dict['thumbnail'])
491 if self.params.get('forcedescription', False) and 'description' in info_dict:
492 compat_print(info_dict['description'])
493 if self.params.get('forcefilename', False) and filename is not None:
494 compat_print(filename)
495 if self.params.get('forceformat', False):
496 compat_print(info_dict['format'])
497
498 # Do nothing else if in simulate mode
499 if self.params.get('simulate', False):
500 return
501
502 if filename is None:
503 return
504
505 try:
506 dn = os.path.dirname(encodeFilename(filename))
507 if dn != '' and not os.path.exists(dn):
508 os.makedirs(dn)
509 except (OSError, IOError) as err:
510 self.report_error(u'unable to create directory ' + compat_str(err))
511 return
512
513 if self.params.get('writedescription', False):
514 try:
515 descfn = filename + u'.description'
516 self.report_writedescription(descfn)
517 with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
518 descfile.write(info_dict['description'])
b3f0e530 519 except (KeyError, TypeError):
535f59bb 520 self.report_warning(u'There\'s no description to write.')
8222d8de
JMF
521 except (OSError, IOError):
522 self.report_error(u'Cannot write description file ' + descfn)
523 return
524
c4a91be7 525 subtitles_are_requested = any([self.params.get('writesubtitles', False),
0b7f3118 526 self.params.get('writeautomaticsub')])
c4a91be7
JMF
527
528 if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:
8222d8de
JMF
529 # subtitles download errors are already managed as troubles in relevant IE
530 # that way it will silently go on when used with unsupporting IE
8222d8de 531 subtitles = info_dict['subtitles']
8222d8de 532 sub_format = self.params.get('subtitlesformat')
5d51a883
JMF
533 for sub_lang in subtitles.keys():
534 sub = subtitles[sub_lang]
6804038d
JMF
535 if sub is None:
536 continue
8222d8de 537 try:
d4051a8e 538 sub_filename = subtitles_filename(filename, sub_lang, sub_format)
8222d8de
JMF
539 self.report_writesubtitles(sub_filename)
540 with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
5d51a883 541 subfile.write(sub)
8222d8de
JMF
542 except (OSError, IOError):
543 self.report_error(u'Cannot write subtitles file ' + descfn)
544 return
545
8222d8de
JMF
546 if self.params.get('writeinfojson', False):
547 infofn = filename + u'.info.json'
548 self.report_writeinfojson(infofn)
549 try:
550 json_info_dict = dict((k, v) for k,v in info_dict.items() if not k in ['urlhandle'])
551 write_json_file(json_info_dict, encodeFilename(infofn))
552 except (OSError, IOError):
553 self.report_error(u'Cannot write metadata to JSON file ' + infofn)
554 return
555
556 if self.params.get('writethumbnail', False):
d8269e1d 557 if info_dict.get('thumbnail') is not None:
cbdbb766 558 thumb_format = determine_ext(info_dict['thumbnail'], u'jpg')
8222d8de
JMF
559 thumb_filename = filename.rpartition('.')[0] + u'.' + thumb_format
560 self.to_screen(u'[%s] %s: Downloading thumbnail ...' %
561 (info_dict['extractor'], info_dict['id']))
0a60edcf
JMF
562 try:
563 uf = compat_urllib_request.urlopen(info_dict['thumbnail'])
564 with open(thumb_filename, 'wb') as thumbf:
565 shutil.copyfileobj(uf, thumbf)
566 self.to_screen(u'[%s] %s: Writing thumbnail to: %s' %
567 (info_dict['extractor'], info_dict['id'], thumb_filename))
568 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
569 self.report_warning(u'Unable to download thumbnail "%s": %s' %
570 (info_dict['thumbnail'], compat_str(err)))
8222d8de
JMF
571
572 if not self.params.get('skip_download', False):
573 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
574 success = True
575 else:
576 try:
577 success = self.fd._do_download(filename, info_dict)
8222d8de
JMF
578 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
579 self.report_error(u'unable to download video data: %s' % str(err))
580 return
c40c6aaa
JMF
581 except (OSError, IOError) as err:
582 raise UnavailableVideoError(err)
8222d8de
JMF
583 except (ContentTooShortError, ) as err:
584 self.report_error(u'content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
585 return
586
587 if success:
588 try:
589 self.post_process(filename, info_dict)
590 except (PostProcessingError) as err:
591 self.report_error(u'postprocessing: %s' % str(err))
592 return
593
c1c9a79c
PH
594 self.record_download_archive(info_dict)
595
8222d8de
JMF
596 def download(self, url_list):
597 """Download a given list of URLs."""
598 if len(url_list) > 1 and self.fixed_template():
599 raise SameFileError(self.params['outtmpl'])
600
601 for url in url_list:
602 try:
603 #It also downloads the videos
604 videos = self.extract_info(url)
605 except UnavailableVideoError:
606 self.report_error(u'unable to download video')
607 except MaxDownloadsReached:
608 self.to_screen(u'[info] Maximum number of downloaded files reached.')
609 raise
610
611 return self._download_retcode
612
613 def post_process(self, filename, ie_info):
614 """Run all the postprocessors on the given file."""
615 info = dict(ie_info)
616 info['filepath'] = filename
617 keep_video = None
618 for pp in self._pps:
619 try:
620 keep_video_wish,new_info = pp.run(info)
621 if keep_video_wish is not None:
622 if keep_video_wish:
623 keep_video = keep_video_wish
624 elif keep_video is None:
625 # No clear decision yet, let IE decide
626 keep_video = keep_video_wish
627 except PostProcessingError as e:
bbcbf4d4 628 self.report_error(e.msg)
8222d8de
JMF
629 if keep_video is False and not self.params.get('keepvideo', False):
630 try:
631 self.to_screen(u'Deleting original file %s (pass -k to keep)' % filename)
632 os.remove(encodeFilename(filename))
633 except (IOError, OSError):
634 self.report_warning(u'Unable to remove downloaded video file')
c1c9a79c
PH
635
636 def in_download_archive(self, info_dict):
637 fn = self.params.get('download_archive')
638 if fn is None:
639 return False
640 vid_id = info_dict['extractor'] + u' ' + info_dict['id']
641 try:
642 with locked_file(fn, 'r', encoding='utf-8') as archive_file:
643 for line in archive_file:
644 if line.strip() == vid_id:
645 return True
646 except IOError as ioe:
647 if ioe.errno != errno.ENOENT:
648 raise
649 return False
650
651 def record_download_archive(self, info_dict):
652 fn = self.params.get('download_archive')
653 if fn is None:
654 return
655 vid_id = info_dict['extractor'] + u' ' + info_dict['id']
656 with locked_file(fn, 'a', encoding='utf-8') as archive_file:
657 archive_file.write(vid_id + u'\n')