2 # -*- coding: utf-8 -*-
4 from __future__ import absolute_import
24 compat_urllib_request,
40 UnavailableVideoError,
44 from .extractor import get_info_extractor, gen_extractors
45 from .FileDownloader import FileDownloader
48 class YoutubeDL(object):
51 YoutubeDL objects are the ones responsible of downloading the
52 actual video file and writing it to disk if the user has requested
53 it, among some other tasks. In most cases there should be one per
54 program. As, given a video URL, the downloader doesn't know how to
55 extract all the needed information, task that InfoExtractors do, it
56 has to pass the URL to one of them.
58 For this, YoutubeDL objects have a method that allows
59 InfoExtractors to be registered in a given order. When it is passed
60 a URL, the YoutubeDL object handles it to the first InfoExtractor it
61 finds that reports being able to handle it. The InfoExtractor extracts
62 all the information about the video or videos the URL refers to, and
63 YoutubeDL process the extracted information, possibly using a File
64 Downloader to download the video.
66 YoutubeDL objects accept a lot of parameters. In order not to saturate
67 the object constructor with arguments, it receives a dictionary of
68 options instead. These options are available through the params
69 attribute for the InfoExtractors to use. The YoutubeDL also
70 registers itself as the downloader in charge for the InfoExtractors
71 that are added to it, so this is a "mutual registration".
75 username: Username for authentication purposes.
76 password: Password for authentication purposes.
77 videopassword: Password for acces a video.
78 usenetrc: Use netrc for authentication instead.
79 verbose: Print additional info to stdout.
80 quiet: Do not print messages to stdout.
81 forceurl: Force printing final URL.
82 forcetitle: Force printing title.
83 forceid: Force printing ID.
84 forcethumbnail: Force printing thumbnail URL.
85 forcedescription: Force printing description.
86 forcefilename: Force printing final filename.
87 simulate: Do not download the video files.
88 format: Video format code.
89 format_limit: Highest quality format to try.
90 outtmpl: Template for output names.
91 restrictfilenames: Do not allow "&" and spaces in file names
92 ignoreerrors: Do not stop on download errors.
93 nooverwrites: Prevent overwriting files.
94 playliststart: Playlist item to start at.
95 playlistend: Playlist item to end at.
96 matchtitle: Download only matching titles.
97 rejecttitle: Reject downloads for matching titles.
98 logtostderr: Log messages to stderr instead of stdout.
99 writedescription: Write the video description to a .description file
100 writeinfojson: Write the video description to a .info.json file
101 writeannotations: Write the video annotations to a .annotations.xml file
102 writethumbnail: Write the thumbnail image to a file
103 writesubtitles: Write the video subtitles to a file
104 writeautomaticsub: Write the automatic subtitles to a file
105 allsubtitles: Downloads all the subtitles of the video
106 (requires writesubtitles or writeautomaticsub)
107 listsubtitles: Lists all available subtitles for the video
108 subtitlesformat: Subtitle format [srt/sbv/vtt] (default=srt)
109 subtitleslangs: List of languages of the subtitles to download
110 keepvideo: Keep the video file after post-processing
111 daterange: A DateRange object, download only if the upload_date is in the range.
112 skip_download: Skip the actual download of the video file
113 cachedir: Location of the cache files in the filesystem.
114 None to disable filesystem cache.
115 noplaylist: Download single video instead of a playlist if in doubt.
116 age_limit: An integer representing the user's age in years.
117 Unsuitable videos for the given age are skipped.
118 downloadarchive: File name of a file where all downloads are recorded.
119 Videos already present in the file are not downloaded
122 The following parameters are not used by YoutubeDL itself, they are used by
124 nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
125 noresizebuffer, retries, continuedl, noprogress, consoletitle
131 _download_retcode = None
132 _num_downloads = None
135 def __init__(self, params):
136 """Create a FileDownloader object with the given options."""
138 self._ies_instances = {}
140 self._progress_hooks = []
141 self._download_retcode = 0
142 self._num_downloads = 0
143 self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
145 if (sys.version_info >= (3,) and sys.platform != 'win32' and
146 sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968']
147 and not params['restrictfilenames']):
148 # On Python 3, the Unicode filesystem API will throw errors (#1474)
150 u'Assuming --restrict-filenames since file system encoding '
151 u'cannot encode all charactes. '
152 u'Set the LC_ALL environment variable to fix this.')
153 params['restrictfilenames'] = True
156 self.fd = FileDownloader(self, self.params)
158 if '%(stitle)s' in self.params['outtmpl']:
159 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.')
161 def add_info_extractor(self, ie):
162 """Add an InfoExtractor object to the end of the list."""
164 self._ies_instances[ie.ie_key()] = ie
165 ie.set_downloader(self)
167 def get_info_extractor(self, ie_key):
169 Get an instance of an IE with name ie_key, it will try to get one from
170 the _ies list, if there's no instance it will create a new one and add
171 it to the extractor list.
173 ie = self._ies_instances.get(ie_key)
175 ie = get_info_extractor(ie_key)()
176 self.add_info_extractor(ie)
179 def add_default_info_extractors(self):
181 Add the InfoExtractors returned by gen_extractors to the end of the list
183 for ie in gen_extractors():
184 self.add_info_extractor(ie)
186 def add_post_processor(self, pp):
187 """Add a PostProcessor object to the end of the chain."""
189 pp.set_downloader(self)
191 def to_screen(self, message, skip_eol=False):
192 """Print message to stdout if not in quiet mode."""
193 if not self.params.get('quiet', False):
194 terminator = [u'\n', u''][skip_eol]
195 output = message + terminator
196 write_string(output, self._screen_file)
198 def to_stderr(self, message):
199 """Print message to stderr."""
200 assert type(message) == type(u'')
201 output = message + u'\n'
202 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
203 output = output.encode(preferredencoding())
204 sys.stderr.write(output)
206 def to_console_title(self, message):
207 if not self.params.get('consoletitle', False):
209 if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
210 # c_wchar_p() might not be necessary if `message` is
211 # already of type unicode()
212 ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
213 elif 'TERM' in os.environ:
214 write_string(u'\033]0;%s\007' % message, self._screen_file)
216 def save_console_title(self):
217 if not self.params.get('consoletitle', False):
219 if 'TERM' in os.environ:
220 # Save the title on stack
221 write_string(u'\033[22;0t', self._screen_file)
223 def restore_console_title(self):
224 if not self.params.get('consoletitle', False):
226 if 'TERM' in os.environ:
227 # Restore the title from stack
228 write_string(u'\033[23;0t', self._screen_file)
231 self.save_console_title()
234 def __exit__(self, *args):
235 self.restore_console_title()
237 def fixed_template(self):
238 """Checks if the output template is fixed."""
239 return (re.search(u'(?u)%\\(.+?\\)s', self.params['outtmpl']) is None)
241 def trouble(self, message=None, tb=None):
242 """Determine action to take when a download problem appears.
244 Depending on if the downloader has been configured to ignore
245 download errors or not, this method may throw an exception or
246 not when errors are found, after printing the message.
248 tb, if given, is additional traceback information.
250 if message is not None:
251 self.to_stderr(message)
252 if self.params.get('verbose'):
254 if sys.exc_info()[0]: # if .trouble has been called from an except block
256 if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
257 tb += u''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
258 tb += compat_str(traceback.format_exc())
260 tb_data = traceback.format_list(traceback.extract_stack())
261 tb = u''.join(tb_data)
263 if not self.params.get('ignoreerrors', False):
264 if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
265 exc_info = sys.exc_info()[1].exc_info
267 exc_info = sys.exc_info()
268 raise DownloadError(message, exc_info)
269 self._download_retcode = 1
271 def report_warning(self, message):
273 Print the message to stderr, it will be prefixed with 'WARNING:'
274 If stderr is a tty file the 'WARNING:' will be colored
276 if sys.stderr.isatty() and os.name != 'nt':
277 _msg_header = u'\033[0;33mWARNING:\033[0m'
279 _msg_header = u'WARNING:'
280 warning_message = u'%s %s' % (_msg_header, message)
281 self.to_stderr(warning_message)
283 def report_error(self, message, tb=None):
285 Do the same as trouble, but prefixes the message with 'ERROR:', colored
286 in red if stderr is a tty file.
288 if sys.stderr.isatty() and os.name != 'nt':
289 _msg_header = u'\033[0;31mERROR:\033[0m'
291 _msg_header = u'ERROR:'
292 error_message = u'%s %s' % (_msg_header, message)
293 self.trouble(error_message, tb)
295 def report_writedescription(self, descfn):
296 """ Report that the description file is being written """
297 self.to_screen(u'[info] Writing video description to: ' + descfn)
299 def report_writesubtitles(self, sub_filename):
300 """ Report that the subtitles file is being written """
301 self.to_screen(u'[info] Writing video subtitles to: ' + sub_filename)
303 def report_writeinfojson(self, infofn):
304 """ Report that the metadata file has been written """
305 self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn)
307 def report_writeannotations(self, annofn):
308 """ Report that the annotations file has been written. """
309 self.to_screen(u'[info] Writing video annotations to: ' + annofn)
311 def report_file_already_downloaded(self, file_name):
312 """Report file has already been fully downloaded."""
314 self.to_screen(u'[download] %s has already been downloaded' % file_name)
315 except UnicodeEncodeError:
316 self.to_screen(u'[download] The file has already been downloaded')
318 def increment_downloads(self):
319 """Increment the ordinal that assigns a number to each file."""
320 self._num_downloads += 1
322 def prepare_filename(self, info_dict):
323 """Generate the output filename."""
325 template_dict = dict(info_dict)
327 template_dict['epoch'] = int(time.time())
328 autonumber_size = self.params.get('autonumber_size')
329 if autonumber_size is None:
331 autonumber_templ = u'%0' + str(autonumber_size) + u'd'
332 template_dict['autonumber'] = autonumber_templ % self._num_downloads
333 if template_dict.get('playlist_index') is not None:
334 template_dict['playlist_index'] = u'%05d' % template_dict['playlist_index']
336 sanitize = lambda k, v: sanitize_filename(
337 u'NA' if v is None else compat_str(v),
338 restricted=self.params.get('restrictfilenames'),
340 template_dict = dict((k, sanitize(k, v))
341 for k, v in template_dict.items())
343 tmpl = os.path.expanduser(self.params['outtmpl'])
344 filename = tmpl % template_dict
346 except KeyError as err:
347 self.report_error(u'Erroneous output template')
349 except ValueError as err:
350 self.report_error(u'Error in output template: ' + str(err) + u' (encoding: ' + repr(preferredencoding()) + ')')
353 def _match_entry(self, info_dict):
354 """ Returns None iff the file should be downloaded """
356 title = info_dict['title']
357 matchtitle = self.params.get('matchtitle', False)
359 if not re.search(matchtitle, title, re.IGNORECASE):
360 return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
361 rejecttitle = self.params.get('rejecttitle', False)
363 if re.search(rejecttitle, title, re.IGNORECASE):
364 return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
365 date = info_dict.get('upload_date', None)
367 dateRange = self.params.get('daterange', DateRange())
368 if date not in dateRange:
369 return u'[download] %s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
370 age_limit = self.params.get('age_limit')
371 if age_limit is not None:
372 if age_limit < info_dict.get('age_limit', 0):
373 return u'Skipping "' + title + '" because it is age restricted'
374 if self.in_download_archive(info_dict):
375 return (u'%(title)s has already been recorded in archive'
380 def add_extra_info(info_dict, extra_info):
381 '''Set the keys from extra_info in info dict if they are missing'''
382 for key, value in extra_info.items():
383 info_dict.setdefault(key, value)
385 def extract_info(self, url, download=True, ie_key=None, extra_info={}):
387 Returns a list with a dictionary for each video we find.
388 If 'download', also downloads the videos.
389 extra_info is a dict containing the extra values to add to each result
393 ies = [self.get_info_extractor(ie_key)]
398 if not ie.suitable(url):
402 self.report_warning(u'The program functionality for this site has been marked as broken, '
403 u'and will probably not work.')
406 ie_result = ie.extract(url)
407 if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
409 if isinstance(ie_result, list):
410 # Backwards compatibility: old IE result format
412 '_type': 'compat_list',
413 'entries': ie_result,
415 self.add_extra_info(ie_result,
417 'extractor': ie.IE_NAME,
419 'extractor_key': ie.ie_key(),
421 return self.process_ie_result(ie_result, download, extra_info)
422 except ExtractorError as de: # An error we somewhat expected
423 self.report_error(compat_str(de), de.format_traceback())
425 except Exception as e:
426 if self.params.get('ignoreerrors', False):
427 self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
432 self.report_error(u'no suitable InfoExtractor: %s' % url)
434 def process_ie_result(self, ie_result, download=True, extra_info={}):
436 Take the result of the ie(may be modified) and resolve all unresolved
437 references (URLs, playlist items).
439 It will also download the videos if 'download'.
440 Returns the resolved ie_result.
443 result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
444 if result_type == 'video':
445 self.add_extra_info(ie_result, extra_info)
446 return self.process_video_result(ie_result, download=download)
447 elif result_type == 'url':
448 # We have to add extra_info to the results because it may be
449 # contained in a playlist
450 return self.extract_info(ie_result['url'],
452 ie_key=ie_result.get('ie_key'),
453 extra_info=extra_info)
454 elif result_type == 'playlist':
455 self.add_extra_info(ie_result, extra_info)
456 # We process each entry in the playlist
457 playlist = ie_result.get('title', None) or ie_result.get('id', None)
458 self.to_screen(u'[download] Downloading playlist: %s' % playlist)
460 playlist_results = []
462 n_all_entries = len(ie_result['entries'])
463 playliststart = self.params.get('playliststart', 1) - 1
464 playlistend = self.params.get('playlistend', -1)
466 if playlistend == -1:
467 entries = ie_result['entries'][playliststart:]
469 entries = ie_result['entries'][playliststart:playlistend]
471 n_entries = len(entries)
473 self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
474 (ie_result['extractor'], playlist, n_all_entries, n_entries))
476 for i, entry in enumerate(entries, 1):
477 self.to_screen(u'[download] Downloading video #%s of %s' % (i, n_entries))
479 'playlist': playlist,
480 'playlist_index': i + playliststart,
481 'extractor': ie_result['extractor'],
482 'webpage_url': ie_result['webpage_url'],
483 'extractor_key': ie_result['extractor_key'],
485 entry_result = self.process_ie_result(entry,
488 playlist_results.append(entry_result)
489 ie_result['entries'] = playlist_results
491 elif result_type == 'compat_list':
493 self.add_extra_info(r,
495 'extractor': ie_result['extractor'],
496 'webpage_url': ie_result['webpage_url'],
497 'extractor_key': ie_result['extractor_key'],
500 ie_result['entries'] = [
501 self.process_ie_result(_fixup(r), download, extra_info)
502 for r in ie_result['entries']
506 raise Exception('Invalid result type: %s' % result_type)
508 def select_format(self, format_spec, available_formats):
509 if format_spec == 'best' or format_spec is None:
510 return available_formats[-1]
511 elif format_spec == 'worst':
512 return available_formats[0]
514 extensions = [u'mp4', u'flv', u'webm', u'3gp']
515 if format_spec in extensions:
516 filter_f = lambda f: f['ext'] == format_spec
518 filter_f = lambda f: f['format_id'] == format_spec
519 matches = list(filter(filter_f, available_formats))
524 def process_video_result(self, info_dict, download=True):
525 assert info_dict.get('_type', 'video') == 'video'
527 if 'playlist' not in info_dict:
528 # It isn't part of a playlist
529 info_dict['playlist'] = None
530 info_dict['playlist_index'] = None
532 # This extractors handle format selection themselves
533 if info_dict['extractor'] in [u'youtube', u'Youku']:
535 self.process_info(info_dict)
538 # We now pick which formats have to be downloaded
539 if info_dict.get('formats') is None:
540 # There's only one format available
541 formats = [info_dict]
543 formats = info_dict['formats']
545 # We check that all the formats have the format and format_id fields
546 for (i, format) in enumerate(formats):
547 if format.get('format_id') is None:
548 format['format_id'] = compat_str(i)
549 if format.get('format') is None:
550 format['format'] = u'{id} - {res}{note}'.format(
551 id=format['format_id'],
552 res=self.format_resolution(format),
553 note=u' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '',
555 # Automatically determine file extension if missing
556 if 'ext' not in format:
557 format['ext'] = determine_ext(format['url'])
559 if self.params.get('listformats', None):
560 self.list_formats(info_dict)
563 format_limit = self.params.get('format_limit', None)
565 formats = list(takewhile_inclusive(
566 lambda f: f['format_id'] != format_limit, formats
568 if self.params.get('prefer_free_formats'):
569 def _free_formats_key(f):
571 ext_ord = [u'flv', u'mp4', u'webm'].index(f['ext'])
574 # We only compare the extension if they have the same height and width
575 return (f.get('height'), f.get('width'), ext_ord)
576 formats = sorted(formats, key=_free_formats_key)
578 req_format = self.params.get('format', 'best')
579 if req_format is None:
581 formats_to_download = []
582 # The -1 is for supporting YoutubeIE
583 if req_format in ('-1', 'all'):
584 formats_to_download = formats
586 # We can accept formats requestd in the format: 34/5/best, we pick
587 # the first that is available, starting from left
588 req_formats = req_format.split('/')
589 for rf in req_formats:
590 selected_format = self.select_format(rf, formats)
591 if selected_format is not None:
592 formats_to_download = [selected_format]
594 if not formats_to_download:
595 raise ExtractorError(u'requested format not available',
599 if len(formats_to_download) > 1:
600 self.to_screen(u'[info] %s: downloading video in %s formats' % (info_dict['id'], len(formats_to_download)))
601 for format in formats_to_download:
602 new_info = dict(info_dict)
603 new_info.update(format)
604 self.process_info(new_info)
605 # We update the info dict with the best quality format (backwards compatibility)
606 info_dict.update(formats_to_download[-1])
609 def process_info(self, info_dict):
610 """Process a single resolved IE result."""
612 assert info_dict.get('_type', 'video') == 'video'
613 #We increment the download the download count here to match the previous behaviour.
614 self.increment_downloads()
616 info_dict['fulltitle'] = info_dict['title']
617 if len(info_dict['title']) > 200:
618 info_dict['title'] = info_dict['title'][:197] + u'...'
620 # Keep for backwards compatibility
621 info_dict['stitle'] = info_dict['title']
623 if not 'format' in info_dict:
624 info_dict['format'] = info_dict['ext']
626 reason = self._match_entry(info_dict)
627 if reason is not None:
628 self.to_screen(u'[download] ' + reason)
631 max_downloads = self.params.get('max_downloads')
632 if max_downloads is not None:
633 if self._num_downloads > int(max_downloads):
634 raise MaxDownloadsReached()
636 filename = self.prepare_filename(info_dict)
639 if self.params.get('forcetitle', False):
640 compat_print(info_dict['title'])
641 if self.params.get('forceid', False):
642 compat_print(info_dict['id'])
643 if self.params.get('forceurl', False):
644 # For RTMP URLs, also include the playpath
645 compat_print(info_dict['url'] + info_dict.get('play_path', u''))
646 if self.params.get('forcethumbnail', False) and info_dict.get('thumbnail') is not None:
647 compat_print(info_dict['thumbnail'])
648 if self.params.get('forcedescription', False) and info_dict.get('description') is not None:
649 compat_print(info_dict['description'])
650 if self.params.get('forcefilename', False) and filename is not None:
651 compat_print(filename)
652 if self.params.get('forceformat', False):
653 compat_print(info_dict['format'])
655 # Do nothing else if in simulate mode
656 if self.params.get('simulate', False):
663 dn = os.path.dirname(encodeFilename(filename))
664 if dn != '' and not os.path.exists(dn):
666 except (OSError, IOError) as err:
667 self.report_error(u'unable to create directory ' + compat_str(err))
670 if self.params.get('writedescription', False):
672 descfn = filename + u'.description'
673 self.report_writedescription(descfn)
674 with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
675 descfile.write(info_dict['description'])
676 except (KeyError, TypeError):
677 self.report_warning(u'There\'s no description to write.')
678 except (OSError, IOError):
679 self.report_error(u'Cannot write description file ' + descfn)
682 if self.params.get('writeannotations', False):
684 annofn = filename + u'.annotations.xml'
685 self.report_writeannotations(annofn)
686 with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile:
687 annofile.write(info_dict['annotations'])
688 except (KeyError, TypeError):
689 self.report_warning(u'There are no annotations to write.')
690 except (OSError, IOError):
691 self.report_error(u'Cannot write annotations file: ' + annofn)
694 subtitles_are_requested = any([self.params.get('writesubtitles', False),
695 self.params.get('writeautomaticsub')])
697 if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:
698 # subtitles download errors are already managed as troubles in relevant IE
699 # that way it will silently go on when used with unsupporting IE
700 subtitles = info_dict['subtitles']
701 sub_format = self.params.get('subtitlesformat', 'srt')
702 for sub_lang in subtitles.keys():
703 sub = subtitles[sub_lang]
707 sub_filename = subtitles_filename(filename, sub_lang, sub_format)
708 self.report_writesubtitles(sub_filename)
709 with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
711 except (OSError, IOError):
712 self.report_error(u'Cannot write subtitles file ' + descfn)
715 if self.params.get('writeinfojson', False):
716 infofn = filename + u'.info.json'
717 self.report_writeinfojson(infofn)
719 json_info_dict = dict((k, v) for k, v in info_dict.items() if not k in ['urlhandle'])
720 write_json_file(json_info_dict, encodeFilename(infofn))
721 except (OSError, IOError):
722 self.report_error(u'Cannot write metadata to JSON file ' + infofn)
725 if self.params.get('writethumbnail', False):
726 if info_dict.get('thumbnail') is not None:
727 thumb_format = determine_ext(info_dict['thumbnail'], u'jpg')
728 thumb_filename = filename.rpartition('.')[0] + u'.' + thumb_format
729 self.to_screen(u'[%s] %s: Downloading thumbnail ...' %
730 (info_dict['extractor'], info_dict['id']))
732 uf = compat_urllib_request.urlopen(info_dict['thumbnail'])
733 with open(thumb_filename, 'wb') as thumbf:
734 shutil.copyfileobj(uf, thumbf)
735 self.to_screen(u'[%s] %s: Writing thumbnail to: %s' %
736 (info_dict['extractor'], info_dict['id'], thumb_filename))
737 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
738 self.report_warning(u'Unable to download thumbnail "%s": %s' %
739 (info_dict['thumbnail'], compat_str(err)))
741 if not self.params.get('skip_download', False):
742 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
746 success = self.fd._do_download(filename, info_dict)
747 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
748 self.report_error(u'unable to download video data: %s' % str(err))
750 except (OSError, IOError) as err:
751 raise UnavailableVideoError(err)
752 except (ContentTooShortError, ) as err:
753 self.report_error(u'content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
758 self.post_process(filename, info_dict)
759 except (PostProcessingError) as err:
760 self.report_error(u'postprocessing: %s' % str(err))
763 self.record_download_archive(info_dict)
765 def download(self, url_list):
766 """Download a given list of URLs."""
767 if len(url_list) > 1 and self.fixed_template():
768 raise SameFileError(self.params['outtmpl'])
772 #It also downloads the videos
773 videos = self.extract_info(url)
774 except UnavailableVideoError:
775 self.report_error(u'unable to download video')
776 except MaxDownloadsReached:
777 self.to_screen(u'[info] Maximum number of downloaded files reached.')
780 return self._download_retcode
782 def post_process(self, filename, ie_info):
783 """Run all the postprocessors on the given file."""
785 info['filepath'] = filename
789 keep_video_wish, new_info = pp.run(info)
790 if keep_video_wish is not None:
792 keep_video = keep_video_wish
793 elif keep_video is None:
794 # No clear decision yet, let IE decide
795 keep_video = keep_video_wish
796 except PostProcessingError as e:
797 self.report_error(e.msg)
798 if keep_video is False and not self.params.get('keepvideo', False):
800 self.to_screen(u'Deleting original file %s (pass -k to keep)' % filename)
801 os.remove(encodeFilename(filename))
802 except (IOError, OSError):
803 self.report_warning(u'Unable to remove downloaded video file')
805 def in_download_archive(self, info_dict):
806 fn = self.params.get('download_archive')
809 vid_id = info_dict['extractor'] + u' ' + info_dict['id']
811 with locked_file(fn, 'r', encoding='utf-8') as archive_file:
812 for line in archive_file:
813 if line.strip() == vid_id:
815 except IOError as ioe:
816 if ioe.errno != errno.ENOENT:
820 def record_download_archive(self, info_dict):
821 fn = self.params.get('download_archive')
824 vid_id = info_dict['extractor'] + u' ' + info_dict['id']
825 with locked_file(fn, 'a', encoding='utf-8') as archive_file:
826 archive_file.write(vid_id + u'\n')
829 def format_resolution(format, default='unknown'):
830 if format.get('_resolution') is not None:
831 return format['_resolution']
832 if format.get('height') is not None:
833 if format.get('width') is not None:
834 res = u'%sx%s' % (format['width'], format['height'])
836 res = u'%sp' % format['height']
841 def list_formats(self, info_dict):
842 def format_note(fdict):
843 if fdict.get('format_note') is not None:
844 return fdict['format_note']
846 if fdict.get('vcodec') is not None:
847 res += u'%-5s' % fdict['vcodec']
848 elif fdict.get('vbr') is not None:
850 if fdict.get('vbr') is not None:
851 res += u'@%4dk' % fdict['vbr']
852 if fdict.get('acodec') is not None:
855 res += u'%-5s' % fdict['acodec']
856 elif fdict.get('abr') is not None:
860 if fdict.get('abr') is not None:
861 res += u'@%3dk' % fdict['abr']
865 return (u'%-20s%-10s%-12s%s' % (
868 self.format_resolution(format),
873 formats = info_dict.get('formats', [info_dict])
874 formats_s = list(map(line, formats))
876 formats_s[0] += (' ' if format_note(formats[0]) else '') + '(worst)'
877 formats_s[-1] += (' ' if format_note(formats[-1]) else '') + '(best)'
880 'format_id': u'format code', 'ext': u'extension',
881 '_resolution': u'resolution', 'format_note': u'note'})
882 self.to_screen(u'[info] Available formats for %s:\n%s\n%s' %
883 (info_dict['id'], header_line, u"\n".join(formats_s)))