2 # -*- coding: utf-8 -*-
4 from __future__ import absolute_import
25 compat_urllib_request,
42 UnavailableVideoError,
46 from .extractor import get_info_extractor, gen_extractors
47 from .FileDownloader import FileDownloader
50 class YoutubeDL(object):
53 YoutubeDL objects are the ones responsible of downloading the
54 actual video file and writing it to disk if the user has requested
55 it, among some other tasks. In most cases there should be one per
56 program. As, given a video URL, the downloader doesn't know how to
57 extract all the needed information, task that InfoExtractors do, it
58 has to pass the URL to one of them.
60 For this, YoutubeDL objects have a method that allows
61 InfoExtractors to be registered in a given order. When it is passed
62 a URL, the YoutubeDL object handles it to the first InfoExtractor it
63 finds that reports being able to handle it. The InfoExtractor extracts
64 all the information about the video or videos the URL refers to, and
65 YoutubeDL process the extracted information, possibly using a File
66 Downloader to download the video.
68 YoutubeDL objects accept a lot of parameters. In order not to saturate
69 the object constructor with arguments, it receives a dictionary of
70 options instead. These options are available through the params
71 attribute for the InfoExtractors to use. The YoutubeDL also
72 registers itself as the downloader in charge for the InfoExtractors
73 that are added to it, so this is a "mutual registration".
77 username: Username for authentication purposes.
78 password: Password for authentication purposes.
79 videopassword: Password for acces a video.
80 usenetrc: Use netrc for authentication instead.
81 verbose: Print additional info to stdout.
82 quiet: Do not print messages to stdout.
83 forceurl: Force printing final URL.
84 forcetitle: Force printing title.
85 forceid: Force printing ID.
86 forcethumbnail: Force printing thumbnail URL.
87 forcedescription: Force printing description.
88 forcefilename: Force printing final filename.
89 forcejson: Force printing info_dict as JSON.
90 simulate: Do not download the video files.
91 format: Video format code.
92 format_limit: Highest quality format to try.
93 outtmpl: Template for output names.
94 restrictfilenames: Do not allow "&" and spaces in file names
95 ignoreerrors: Do not stop on download errors.
96 nooverwrites: Prevent overwriting files.
97 playliststart: Playlist item to start at.
98 playlistend: Playlist item to end at.
99 matchtitle: Download only matching titles.
100 rejecttitle: Reject downloads for matching titles.
101 logger: Log messages to a logging.Logger instance.
102 logtostderr: Log messages to stderr instead of stdout.
103 writedescription: Write the video description to a .description file
104 writeinfojson: Write the video description to a .info.json file
105 writeannotations: Write the video annotations to a .annotations.xml file
106 writethumbnail: Write the thumbnail image to a file
107 writesubtitles: Write the video subtitles to a file
108 writeautomaticsub: Write the automatic subtitles to a file
109 allsubtitles: Downloads all the subtitles of the video
110 (requires writesubtitles or writeautomaticsub)
111 listsubtitles: Lists all available subtitles for the video
112 subtitlesformat: Subtitle format [srt/sbv/vtt] (default=srt)
113 subtitleslangs: List of languages of the subtitles to download
114 keepvideo: Keep the video file after post-processing
115 daterange: A DateRange object, download only if the upload_date is in the range.
116 skip_download: Skip the actual download of the video file
117 cachedir: Location of the cache files in the filesystem.
118 None to disable filesystem cache.
119 noplaylist: Download single video instead of a playlist if in doubt.
120 age_limit: An integer representing the user's age in years.
121 Unsuitable videos for the given age are skipped.
122 downloadarchive: File name of a file where all downloads are recorded.
123 Videos already present in the file are not downloaded
126 The following parameters are not used by YoutubeDL itself, they are used by
128 nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
129 noresizebuffer, retries, continuedl, noprogress, consoletitle
135 _download_retcode = None
136 _num_downloads = None
139 def __init__(self, params):
140 """Create a FileDownloader object with the given options."""
142 self._ies_instances = {}
144 self._progress_hooks = []
145 self._download_retcode = 0
146 self._num_downloads = 0
147 self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
149 if (sys.version_info >= (3,) and sys.platform != 'win32' and
150 sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968']
151 and not params['restrictfilenames']):
152 # On Python 3, the Unicode filesystem API will throw errors (#1474)
154 u'Assuming --restrict-filenames since file system encoding '
155 u'cannot encode all charactes. '
156 u'Set the LC_ALL environment variable to fix this.')
157 params['restrictfilenames'] = True
160 self.fd = FileDownloader(self, self.params)
162 if '%(stitle)s' in self.params['outtmpl']:
163 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.')
165 def add_info_extractor(self, ie):
166 """Add an InfoExtractor object to the end of the list."""
168 self._ies_instances[ie.ie_key()] = ie
169 ie.set_downloader(self)
171 def get_info_extractor(self, ie_key):
173 Get an instance of an IE with name ie_key, it will try to get one from
174 the _ies list, if there's no instance it will create a new one and add
175 it to the extractor list.
177 ie = self._ies_instances.get(ie_key)
179 ie = get_info_extractor(ie_key)()
180 self.add_info_extractor(ie)
183 def add_default_info_extractors(self):
185 Add the InfoExtractors returned by gen_extractors to the end of the list
187 for ie in gen_extractors():
188 self.add_info_extractor(ie)
190 def add_post_processor(self, pp):
191 """Add a PostProcessor object to the end of the chain."""
193 pp.set_downloader(self)
195 def to_screen(self, message, skip_eol=False):
196 """Print message to stdout if not in quiet mode."""
197 if self.params.get('logger'):
198 self.params['logger'].debug(message)
199 elif not self.params.get('quiet', False):
200 terminator = [u'\n', u''][skip_eol]
201 output = message + terminator
202 write_string(output, self._screen_file)
204 def to_stderr(self, message):
205 """Print message to stderr."""
206 assert type(message) == type(u'')
207 if self.params.get('logger'):
208 self.params['logger'].error(message)
210 output = message + u'\n'
211 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
212 output = output.encode(preferredencoding())
213 sys.stderr.write(output)
215 def to_console_title(self, message):
216 if not self.params.get('consoletitle', False):
218 if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
219 # c_wchar_p() might not be necessary if `message` is
220 # already of type unicode()
221 ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
222 elif 'TERM' in os.environ:
223 write_string(u'\033]0;%s\007' % message, self._screen_file)
225 def save_console_title(self):
226 if not self.params.get('consoletitle', False):
228 if 'TERM' in os.environ:
229 # Save the title on stack
230 write_string(u'\033[22;0t', self._screen_file)
232 def restore_console_title(self):
233 if not self.params.get('consoletitle', False):
235 if 'TERM' in os.environ:
236 # Restore the title from stack
237 write_string(u'\033[23;0t', self._screen_file)
240 self.save_console_title()
243 def __exit__(self, *args):
244 self.restore_console_title()
246 def fixed_template(self):
247 """Checks if the output template is fixed."""
248 return (re.search(u'(?u)%\\(.+?\\)s', self.params['outtmpl']) is None)
250 def trouble(self, message=None, tb=None):
251 """Determine action to take when a download problem appears.
253 Depending on if the downloader has been configured to ignore
254 download errors or not, this method may throw an exception or
255 not when errors are found, after printing the message.
257 tb, if given, is additional traceback information.
259 if message is not None:
260 self.to_stderr(message)
261 if self.params.get('verbose'):
263 if sys.exc_info()[0]: # if .trouble has been called from an except block
265 if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
266 tb += u''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
267 tb += compat_str(traceback.format_exc())
269 tb_data = traceback.format_list(traceback.extract_stack())
270 tb = u''.join(tb_data)
272 if not self.params.get('ignoreerrors', False):
273 if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
274 exc_info = sys.exc_info()[1].exc_info
276 exc_info = sys.exc_info()
277 raise DownloadError(message, exc_info)
278 self._download_retcode = 1
280 def report_warning(self, message):
282 Print the message to stderr, it will be prefixed with 'WARNING:'
283 If stderr is a tty file the 'WARNING:' will be colored
285 if sys.stderr.isatty() and os.name != 'nt':
286 _msg_header = u'\033[0;33mWARNING:\033[0m'
288 _msg_header = u'WARNING:'
289 warning_message = u'%s %s' % (_msg_header, message)
290 self.to_stderr(warning_message)
292 def report_error(self, message, tb=None):
294 Do the same as trouble, but prefixes the message with 'ERROR:', colored
295 in red if stderr is a tty file.
297 if sys.stderr.isatty() and os.name != 'nt':
298 _msg_header = u'\033[0;31mERROR:\033[0m'
300 _msg_header = u'ERROR:'
301 error_message = u'%s %s' % (_msg_header, message)
302 self.trouble(error_message, tb)
304 def report_writedescription(self, descfn):
305 """ Report that the description file is being written """
306 self.to_screen(u'[info] Writing video description to: ' + descfn)
308 def report_writesubtitles(self, sub_filename):
309 """ Report that the subtitles file is being written """
310 self.to_screen(u'[info] Writing video subtitles to: ' + sub_filename)
312 def report_writeinfojson(self, infofn):
313 """ Report that the metadata file has been written """
314 self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn)
316 def report_writeannotations(self, annofn):
317 """ Report that the annotations file has been written. """
318 self.to_screen(u'[info] Writing video annotations to: ' + annofn)
320 def report_file_already_downloaded(self, file_name):
321 """Report file has already been fully downloaded."""
323 self.to_screen(u'[download] %s has already been downloaded' % file_name)
324 except UnicodeEncodeError:
325 self.to_screen(u'[download] The file has already been downloaded')
327 def increment_downloads(self):
328 """Increment the ordinal that assigns a number to each file."""
329 self._num_downloads += 1
331 def prepare_filename(self, info_dict):
332 """Generate the output filename."""
334 template_dict = dict(info_dict)
336 template_dict['epoch'] = int(time.time())
337 autonumber_size = self.params.get('autonumber_size')
338 if autonumber_size is None:
340 autonumber_templ = u'%0' + str(autonumber_size) + u'd'
341 template_dict['autonumber'] = autonumber_templ % self._num_downloads
342 if template_dict.get('playlist_index') is not None:
343 template_dict['playlist_index'] = u'%05d' % template_dict['playlist_index']
345 sanitize = lambda k, v: sanitize_filename(
346 u'NA' if v is None else compat_str(v),
347 restricted=self.params.get('restrictfilenames'),
349 template_dict = dict((k, sanitize(k, v))
350 for k, v in template_dict.items())
352 tmpl = os.path.expanduser(self.params['outtmpl'])
353 filename = tmpl % template_dict
355 except KeyError as err:
356 self.report_error(u'Erroneous output template')
358 except ValueError as err:
359 self.report_error(u'Error in output template: ' + str(err) + u' (encoding: ' + repr(preferredencoding()) + ')')
362 def _match_entry(self, info_dict):
363 """ Returns None iff the file should be downloaded """
365 if 'title' in info_dict:
366 # This can happen when we're just evaluating the playlist
367 title = info_dict['title']
368 matchtitle = self.params.get('matchtitle', False)
370 if not re.search(matchtitle, title, re.IGNORECASE):
371 return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
372 rejecttitle = self.params.get('rejecttitle', False)
374 if re.search(rejecttitle, title, re.IGNORECASE):
375 return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
376 date = info_dict.get('upload_date', None)
378 dateRange = self.params.get('daterange', DateRange())
379 if date not in dateRange:
380 return u'[download] %s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
381 age_limit = self.params.get('age_limit')
382 if age_limit is not None:
383 if age_limit < info_dict.get('age_limit', 0):
384 return u'Skipping "' + title + '" because it is age restricted'
385 if self.in_download_archive(info_dict):
386 return (u'%s has already been recorded in archive'
387 % info_dict.get('title', info_dict.get('id', u'video')))
391 def add_extra_info(info_dict, extra_info):
392 '''Set the keys from extra_info in info dict if they are missing'''
393 for key, value in extra_info.items():
394 info_dict.setdefault(key, value)
396 def extract_info(self, url, download=True, ie_key=None, extra_info={}):
398 Returns a list with a dictionary for each video we find.
399 If 'download', also downloads the videos.
400 extra_info is a dict containing the extra values to add to each result
404 ies = [self.get_info_extractor(ie_key)]
409 if not ie.suitable(url):
413 self.report_warning(u'The program functionality for this site has been marked as broken, '
414 u'and will probably not work.')
417 ie_result = ie.extract(url)
418 if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
420 if isinstance(ie_result, list):
421 # Backwards compatibility: old IE result format
423 '_type': 'compat_list',
424 'entries': ie_result,
426 self.add_extra_info(ie_result,
428 'extractor': ie.IE_NAME,
430 'extractor_key': ie.ie_key(),
432 return self.process_ie_result(ie_result, download, extra_info)
433 except ExtractorError as de: # An error we somewhat expected
434 self.report_error(compat_str(de), de.format_traceback())
436 except Exception as e:
437 if self.params.get('ignoreerrors', False):
438 self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
443 self.report_error(u'no suitable InfoExtractor: %s' % url)
445 def process_ie_result(self, ie_result, download=True, extra_info={}):
447 Take the result of the ie(may be modified) and resolve all unresolved
448 references (URLs, playlist items).
450 It will also download the videos if 'download'.
451 Returns the resolved ie_result.
454 result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
455 if result_type == 'video':
456 self.add_extra_info(ie_result, extra_info)
457 return self.process_video_result(ie_result, download=download)
458 elif result_type == 'url':
459 # We have to add extra_info to the results because it may be
460 # contained in a playlist
461 return self.extract_info(ie_result['url'],
463 ie_key=ie_result.get('ie_key'),
464 extra_info=extra_info)
465 elif result_type == 'playlist':
467 # We process each entry in the playlist
468 playlist = ie_result.get('title', None) or ie_result.get('id', None)
469 self.to_screen(u'[download] Downloading playlist: %s' % playlist)
471 playlist_results = []
473 n_all_entries = len(ie_result['entries'])
474 playliststart = self.params.get('playliststart', 1) - 1
475 playlistend = self.params.get('playlistend', -1)
477 if playlistend == -1:
478 entries = ie_result['entries'][playliststart:]
480 entries = ie_result['entries'][playliststart:playlistend]
482 n_entries = len(entries)
484 self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
485 (ie_result['extractor'], playlist, n_all_entries, n_entries))
487 for i, entry in enumerate(entries, 1):
488 self.to_screen(u'[download] Downloading video #%s of %s' % (i, n_entries))
490 'playlist': playlist,
491 'playlist_index': i + playliststart,
492 'extractor': ie_result['extractor'],
493 'webpage_url': ie_result['webpage_url'],
494 'extractor_key': ie_result['extractor_key'],
497 reason = self._match_entry(entry)
498 if reason is not None:
499 self.to_screen(u'[download] ' + reason)
502 entry_result = self.process_ie_result(entry,
505 playlist_results.append(entry_result)
506 ie_result['entries'] = playlist_results
508 elif result_type == 'compat_list':
510 self.add_extra_info(r,
512 'extractor': ie_result['extractor'],
513 'webpage_url': ie_result['webpage_url'],
514 'extractor_key': ie_result['extractor_key'],
517 ie_result['entries'] = [
518 self.process_ie_result(_fixup(r), download, extra_info)
519 for r in ie_result['entries']
523 raise Exception('Invalid result type: %s' % result_type)
525 def select_format(self, format_spec, available_formats):
526 if format_spec == 'best' or format_spec is None:
527 return available_formats[-1]
528 elif format_spec == 'worst':
529 return available_formats[0]
531 extensions = [u'mp4', u'flv', u'webm', u'3gp']
532 if format_spec in extensions:
533 filter_f = lambda f: f['ext'] == format_spec
535 filter_f = lambda f: f['format_id'] == format_spec
536 matches = list(filter(filter_f, available_formats))
541 def process_video_result(self, info_dict, download=True):
542 assert info_dict.get('_type', 'video') == 'video'
544 if 'playlist' not in info_dict:
545 # It isn't part of a playlist
546 info_dict['playlist'] = None
547 info_dict['playlist_index'] = None
549 # This extractors handle format selection themselves
550 if info_dict['extractor'] in [u'youtube', u'Youku']:
552 self.process_info(info_dict)
555 # We now pick which formats have to be downloaded
556 if info_dict.get('formats') is None:
557 # There's only one format available
558 formats = [info_dict]
560 formats = info_dict['formats']
562 # We check that all the formats have the format and format_id fields
563 for (i, format) in enumerate(formats):
564 if format.get('format_id') is None:
565 format['format_id'] = compat_str(i)
566 if format.get('format') is None:
567 format['format'] = u'{id} - {res}{note}'.format(
568 id=format['format_id'],
569 res=self.format_resolution(format),
570 note=u' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '',
572 # Automatically determine file extension if missing
573 if 'ext' not in format:
574 format['ext'] = determine_ext(format['url'])
576 if self.params.get('listformats', None):
577 self.list_formats(info_dict)
580 format_limit = self.params.get('format_limit', None)
582 formats = list(takewhile_inclusive(
583 lambda f: f['format_id'] != format_limit, formats
585 if self.params.get('prefer_free_formats'):
586 def _free_formats_key(f):
588 ext_ord = [u'flv', u'mp4', u'webm'].index(f['ext'])
591 # We only compare the extension if they have the same height and width
592 return (f.get('height'), f.get('width'), ext_ord)
593 formats = sorted(formats, key=_free_formats_key)
595 req_format = self.params.get('format', 'best')
596 if req_format is None:
598 formats_to_download = []
599 # The -1 is for supporting YoutubeIE
600 if req_format in ('-1', 'all'):
601 formats_to_download = formats
603 # We can accept formats requestd in the format: 34/5/best, we pick
604 # the first that is available, starting from left
605 req_formats = req_format.split('/')
606 for rf in req_formats:
607 selected_format = self.select_format(rf, formats)
608 if selected_format is not None:
609 formats_to_download = [selected_format]
611 if not formats_to_download:
612 raise ExtractorError(u'requested format not available',
616 if len(formats_to_download) > 1:
617 self.to_screen(u'[info] %s: downloading video in %s formats' % (info_dict['id'], len(formats_to_download)))
618 for format in formats_to_download:
619 new_info = dict(info_dict)
620 new_info.update(format)
621 self.process_info(new_info)
622 # We update the info dict with the best quality format (backwards compatibility)
623 info_dict.update(formats_to_download[-1])
626 def process_info(self, info_dict):
627 """Process a single resolved IE result."""
629 assert info_dict.get('_type', 'video') == 'video'
630 #We increment the download the download count here to match the previous behaviour.
631 self.increment_downloads()
633 info_dict['fulltitle'] = info_dict['title']
634 if len(info_dict['title']) > 200:
635 info_dict['title'] = info_dict['title'][:197] + u'...'
637 # Keep for backwards compatibility
638 info_dict['stitle'] = info_dict['title']
640 if not 'format' in info_dict:
641 info_dict['format'] = info_dict['ext']
643 reason = self._match_entry(info_dict)
644 if reason is not None:
645 self.to_screen(u'[download] ' + reason)
648 max_downloads = self.params.get('max_downloads')
649 if max_downloads is not None:
650 if self._num_downloads > int(max_downloads):
651 raise MaxDownloadsReached()
653 filename = self.prepare_filename(info_dict)
656 if self.params.get('forcetitle', False):
657 compat_print(info_dict['fulltitle'])
658 if self.params.get('forceid', False):
659 compat_print(info_dict['id'])
660 if self.params.get('forceurl', False):
661 # For RTMP URLs, also include the playpath
662 compat_print(info_dict['url'] + info_dict.get('play_path', u''))
663 if self.params.get('forcethumbnail', False) and info_dict.get('thumbnail') is not None:
664 compat_print(info_dict['thumbnail'])
665 if self.params.get('forcedescription', False) and info_dict.get('description') is not None:
666 compat_print(info_dict['description'])
667 if self.params.get('forcefilename', False) and filename is not None:
668 compat_print(filename)
669 if self.params.get('forceformat', False):
670 compat_print(info_dict['format'])
671 if self.params.get('forcejson', False):
672 compat_print(json.dumps(info_dict))
674 # Do nothing else if in simulate mode
675 if self.params.get('simulate', False):
682 dn = os.path.dirname(encodeFilename(filename))
683 if dn != '' and not os.path.exists(dn):
685 except (OSError, IOError) as err:
686 self.report_error(u'unable to create directory ' + compat_str(err))
689 if self.params.get('writedescription', False):
691 descfn = filename + u'.description'
692 self.report_writedescription(descfn)
693 with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
694 descfile.write(info_dict['description'])
695 except (KeyError, TypeError):
696 self.report_warning(u'There\'s no description to write.')
697 except (OSError, IOError):
698 self.report_error(u'Cannot write description file ' + descfn)
701 if self.params.get('writeannotations', False):
703 annofn = filename + u'.annotations.xml'
704 self.report_writeannotations(annofn)
705 with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile:
706 annofile.write(info_dict['annotations'])
707 except (KeyError, TypeError):
708 self.report_warning(u'There are no annotations to write.')
709 except (OSError, IOError):
710 self.report_error(u'Cannot write annotations file: ' + annofn)
713 subtitles_are_requested = any([self.params.get('writesubtitles', False),
714 self.params.get('writeautomaticsub')])
716 if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:
717 # subtitles download errors are already managed as troubles in relevant IE
718 # that way it will silently go on when used with unsupporting IE
719 subtitles = info_dict['subtitles']
720 sub_format = self.params.get('subtitlesformat', 'srt')
721 for sub_lang in subtitles.keys():
722 sub = subtitles[sub_lang]
726 sub_filename = subtitles_filename(filename, sub_lang, sub_format)
727 self.report_writesubtitles(sub_filename)
728 with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
730 except (OSError, IOError):
731 self.report_error(u'Cannot write subtitles file ' + descfn)
734 if self.params.get('writeinfojson', False):
735 infofn = os.path.splitext(filename)[0] + u'.info.json'
736 self.report_writeinfojson(infofn)
738 json_info_dict = dict((k, v) for k, v in info_dict.items() if not k in ['urlhandle'])
739 write_json_file(json_info_dict, encodeFilename(infofn))
740 except (OSError, IOError):
741 self.report_error(u'Cannot write metadata to JSON file ' + infofn)
744 if self.params.get('writethumbnail', False):
745 if info_dict.get('thumbnail') is not None:
746 thumb_format = determine_ext(info_dict['thumbnail'], u'jpg')
747 thumb_filename = filename.rpartition('.')[0] + u'.' + thumb_format
748 self.to_screen(u'[%s] %s: Downloading thumbnail ...' %
749 (info_dict['extractor'], info_dict['id']))
751 uf = compat_urllib_request.urlopen(info_dict['thumbnail'])
752 with open(thumb_filename, 'wb') as thumbf:
753 shutil.copyfileobj(uf, thumbf)
754 self.to_screen(u'[%s] %s: Writing thumbnail to: %s' %
755 (info_dict['extractor'], info_dict['id'], thumb_filename))
756 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
757 self.report_warning(u'Unable to download thumbnail "%s": %s' %
758 (info_dict['thumbnail'], compat_str(err)))
760 if not self.params.get('skip_download', False):
761 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
765 success = self.fd._do_download(filename, info_dict)
766 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
767 self.report_error(u'unable to download video data: %s' % str(err))
769 except (OSError, IOError) as err:
770 raise UnavailableVideoError(err)
771 except (ContentTooShortError, ) as err:
772 self.report_error(u'content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
777 self.post_process(filename, info_dict)
778 except (PostProcessingError) as err:
779 self.report_error(u'postprocessing: %s' % str(err))
782 self.record_download_archive(info_dict)
784 def download(self, url_list):
785 """Download a given list of URLs."""
786 if len(url_list) > 1 and self.fixed_template():
787 raise SameFileError(self.params['outtmpl'])
791 #It also downloads the videos
792 videos = self.extract_info(url)
793 except UnavailableVideoError:
794 self.report_error(u'unable to download video')
795 except MaxDownloadsReached:
796 self.to_screen(u'[info] Maximum number of downloaded files reached.')
799 return self._download_retcode
801 def post_process(self, filename, ie_info):
802 """Run all the postprocessors on the given file."""
804 info['filepath'] = filename
808 keep_video_wish, new_info = pp.run(info)
809 if keep_video_wish is not None:
811 keep_video = keep_video_wish
812 elif keep_video is None:
813 # No clear decision yet, let IE decide
814 keep_video = keep_video_wish
815 except PostProcessingError as e:
816 self.report_error(e.msg)
817 if keep_video is False and not self.params.get('keepvideo', False):
819 self.to_screen(u'Deleting original file %s (pass -k to keep)' % filename)
820 os.remove(encodeFilename(filename))
821 except (IOError, OSError):
822 self.report_warning(u'Unable to remove downloaded video file')
824 def in_download_archive(self, info_dict):
825 fn = self.params.get('download_archive')
828 extractor = info_dict.get('extractor_id')
829 if extractor is None:
830 if 'id' in info_dict:
831 extractor = info_dict.get('ie_key') # key in a playlist
832 if extractor is None:
833 return False # Incomplete video information
834 # Future-proof against any change in case
835 # and backwards compatibility with prior versions
836 extractor = extractor.lower()
837 vid_id = extractor + u' ' + info_dict['id']
839 with locked_file(fn, 'r', encoding='utf-8') as archive_file:
840 for line in archive_file:
841 if line.strip() == vid_id:
843 except IOError as ioe:
844 if ioe.errno != errno.ENOENT:
848 def record_download_archive(self, info_dict):
849 fn = self.params.get('download_archive')
852 vid_id = info_dict['extractor'] + u' ' + info_dict['id']
853 with locked_file(fn, 'a', encoding='utf-8') as archive_file:
854 archive_file.write(vid_id + u'\n')
857 def format_resolution(format, default='unknown'):
858 if format.get('_resolution') is not None:
859 return format['_resolution']
860 if format.get('height') is not None:
861 if format.get('width') is not None:
862 res = u'%sx%s' % (format['width'], format['height'])
864 res = u'%sp' % format['height']
869 def list_formats(self, info_dict):
870 def format_note(fdict):
872 if fdict.get('format_note') is not None:
873 res += fdict['format_note'] + u' '
874 if fdict.get('vcodec') is not None:
875 res += u'%-5s' % fdict['vcodec']
876 elif fdict.get('vbr') is not None:
878 if fdict.get('vbr') is not None:
879 res += u'@%4dk' % fdict['vbr']
880 if fdict.get('acodec') is not None:
883 res += u'%-5s' % fdict['acodec']
884 elif fdict.get('abr') is not None:
888 if fdict.get('abr') is not None:
889 res += u'@%3dk' % fdict['abr']
890 if fdict.get('filesize') is not None:
893 res += format_bytes(fdict['filesize'])
896 def line(format, idlen=20):
897 return ((u'%-' + compat_str(idlen + 1) + u's%-10s%-12s%s') % (
900 self.format_resolution(format),
904 formats = info_dict.get('formats', [info_dict])
905 idlen = max(len(u'format code'),
906 max(len(f['format_id']) for f in formats))
907 formats_s = [line(f, idlen) for f in formats]
909 formats_s[0] += (' ' if format_note(formats[0]) else '') + '(worst)'
910 formats_s[-1] += (' ' if format_note(formats[-1]) else '') + '(best)'
913 'format_id': u'format code', 'ext': u'extension',
914 '_resolution': u'resolution', 'format_note': u'note'}, idlen=idlen)
915 self.to_screen(u'[info] Available formats for %s:\n%s\n%s' %
916 (info_dict['id'], header_line, u"\n".join(formats_s)))