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 self.to_screen('\033]0;%s\007' % message, skip_eol=True)
216 def fixed_template(self):
217 """Checks if the output template is fixed."""
218 return (re.search(u'(?u)%\\(.+?\\)s', self.params['outtmpl']) is None)
220 def trouble(self, message=None, tb=None):
221 """Determine action to take when a download problem appears.
223 Depending on if the downloader has been configured to ignore
224 download errors or not, this method may throw an exception or
225 not when errors are found, after printing the message.
227 tb, if given, is additional traceback information.
229 if message is not None:
230 self.to_stderr(message)
231 if self.params.get('verbose'):
233 if sys.exc_info()[0]: # if .trouble has been called from an except block
235 if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
236 tb += u''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
237 tb += compat_str(traceback.format_exc())
239 tb_data = traceback.format_list(traceback.extract_stack())
240 tb = u''.join(tb_data)
242 if not self.params.get('ignoreerrors', False):
243 if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
244 exc_info = sys.exc_info()[1].exc_info
246 exc_info = sys.exc_info()
247 raise DownloadError(message, exc_info)
248 self._download_retcode = 1
250 def report_warning(self, message):
252 Print the message to stderr, it will be prefixed with 'WARNING:'
253 If stderr is a tty file the 'WARNING:' will be colored
255 if sys.stderr.isatty() and os.name != 'nt':
256 _msg_header = u'\033[0;33mWARNING:\033[0m'
258 _msg_header = u'WARNING:'
259 warning_message = u'%s %s' % (_msg_header, message)
260 self.to_stderr(warning_message)
262 def report_error(self, message, tb=None):
264 Do the same as trouble, but prefixes the message with 'ERROR:', colored
265 in red if stderr is a tty file.
267 if sys.stderr.isatty() and os.name != 'nt':
268 _msg_header = u'\033[0;31mERROR:\033[0m'
270 _msg_header = u'ERROR:'
271 error_message = u'%s %s' % (_msg_header, message)
272 self.trouble(error_message, tb)
274 def report_writedescription(self, descfn):
275 """ Report that the description file is being written """
276 self.to_screen(u'[info] Writing video description to: ' + descfn)
278 def report_writesubtitles(self, sub_filename):
279 """ Report that the subtitles file is being written """
280 self.to_screen(u'[info] Writing video subtitles to: ' + sub_filename)
282 def report_writeinfojson(self, infofn):
283 """ Report that the metadata file has been written """
284 self.to_screen(u'[info] Video description metadata as JSON to: ' + infofn)
286 def report_writeannotations(self, annofn):
287 """ Report that the annotations file has been written. """
288 self.to_screen(u'[info] Writing video annotations to: ' + annofn)
290 def report_file_already_downloaded(self, file_name):
291 """Report file has already been fully downloaded."""
293 self.to_screen(u'[download] %s has already been downloaded' % file_name)
294 except UnicodeEncodeError:
295 self.to_screen(u'[download] The file has already been downloaded')
297 def increment_downloads(self):
298 """Increment the ordinal that assigns a number to each file."""
299 self._num_downloads += 1
301 def prepare_filename(self, info_dict):
302 """Generate the output filename."""
304 template_dict = dict(info_dict)
306 template_dict['epoch'] = int(time.time())
307 autonumber_size = self.params.get('autonumber_size')
308 if autonumber_size is None:
310 autonumber_templ = u'%0' + str(autonumber_size) + u'd'
311 template_dict['autonumber'] = autonumber_templ % self._num_downloads
312 if template_dict.get('playlist_index') is not None:
313 template_dict['playlist_index'] = u'%05d' % template_dict['playlist_index']
315 sanitize = lambda k, v: sanitize_filename(
316 u'NA' if v is None else compat_str(v),
317 restricted=self.params.get('restrictfilenames'),
319 template_dict = dict((k, sanitize(k, v))
320 for k, v in template_dict.items())
322 tmpl = os.path.expanduser(self.params['outtmpl'])
323 filename = tmpl % template_dict
325 except KeyError as err:
326 self.report_error(u'Erroneous output template')
328 except ValueError as err:
329 self.report_error(u'Error in output template: ' + str(err) + u' (encoding: ' + repr(preferredencoding()) + ')')
332 def _match_entry(self, info_dict):
333 """ Returns None iff the file should be downloaded """
335 title = info_dict['title']
336 matchtitle = self.params.get('matchtitle', False)
338 if not re.search(matchtitle, title, re.IGNORECASE):
339 return u'[download] "' + title + '" title did not match pattern "' + matchtitle + '"'
340 rejecttitle = self.params.get('rejecttitle', False)
342 if re.search(rejecttitle, title, re.IGNORECASE):
343 return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
344 date = info_dict.get('upload_date', None)
346 dateRange = self.params.get('daterange', DateRange())
347 if date not in dateRange:
348 return u'[download] %s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
349 age_limit = self.params.get('age_limit')
350 if age_limit is not None:
351 if age_limit < info_dict.get('age_limit', 0):
352 return u'Skipping "' + title + '" because it is age restricted'
353 if self.in_download_archive(info_dict):
354 return (u'%(title)s has already been recorded in archive'
359 def add_extra_info(info_dict, extra_info):
360 '''Set the keys from extra_info in info dict if they are missing'''
361 for key, value in extra_info.items():
362 info_dict.setdefault(key, value)
364 def extract_info(self, url, download=True, ie_key=None, extra_info={}):
366 Returns a list with a dictionary for each video we find.
367 If 'download', also downloads the videos.
368 extra_info is a dict containing the extra values to add to each result
372 ies = [self.get_info_extractor(ie_key)]
377 if not ie.suitable(url):
381 self.report_warning(u'The program functionality for this site has been marked as broken, '
382 u'and will probably not work.')
385 ie_result = ie.extract(url)
386 if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
388 if isinstance(ie_result, list):
389 # Backwards compatibility: old IE result format
391 '_type': 'compat_list',
392 'entries': ie_result,
394 self.add_extra_info(ie_result,
396 'extractor': ie.IE_NAME,
398 'extractor_key': ie.ie_key(),
400 return self.process_ie_result(ie_result, download, extra_info)
401 except ExtractorError as de: # An error we somewhat expected
402 self.report_error(compat_str(de), de.format_traceback())
404 except Exception as e:
405 if self.params.get('ignoreerrors', False):
406 self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
411 self.report_error(u'no suitable InfoExtractor: %s' % url)
413 def process_ie_result(self, ie_result, download=True, extra_info={}):
415 Take the result of the ie(may be modified) and resolve all unresolved
416 references (URLs, playlist items).
418 It will also download the videos if 'download'.
419 Returns the resolved ie_result.
422 result_type = ie_result.get('_type', 'video') # If not given we suppose it's a video, support the default old system
423 if result_type == 'video':
424 self.add_extra_info(ie_result, extra_info)
425 return self.process_video_result(ie_result, download=download)
426 elif result_type == 'url':
427 # We have to add extra_info to the results because it may be
428 # contained in a playlist
429 return self.extract_info(ie_result['url'],
431 ie_key=ie_result.get('ie_key'),
432 extra_info=extra_info)
433 elif result_type == 'playlist':
434 self.add_extra_info(ie_result, extra_info)
435 # We process each entry in the playlist
436 playlist = ie_result.get('title', None) or ie_result.get('id', None)
437 self.to_screen(u'[download] Downloading playlist: %s' % playlist)
439 playlist_results = []
441 n_all_entries = len(ie_result['entries'])
442 playliststart = self.params.get('playliststart', 1) - 1
443 playlistend = self.params.get('playlistend', -1)
445 if playlistend == -1:
446 entries = ie_result['entries'][playliststart:]
448 entries = ie_result['entries'][playliststart:playlistend]
450 n_entries = len(entries)
452 self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
453 (ie_result['extractor'], playlist, n_all_entries, n_entries))
455 for i, entry in enumerate(entries, 1):
456 self.to_screen(u'[download] Downloading video #%s of %s' % (i, n_entries))
458 'playlist': playlist,
459 'playlist_index': i + playliststart,
460 'extractor': ie_result['extractor'],
461 'webpage_url': ie_result['webpage_url'],
462 'extractor_key': ie_result['extractor_key'],
464 entry_result = self.process_ie_result(entry,
467 playlist_results.append(entry_result)
468 ie_result['entries'] = playlist_results
470 elif result_type == 'compat_list':
472 self.add_extra_info(r,
474 'extractor': ie_result['extractor'],
475 'webpage_url': ie_result['webpage_url'],
476 'extractor_key': ie_result['extractor_key'],
479 ie_result['entries'] = [
480 self.process_ie_result(_fixup(r), download, extra_info)
481 for r in ie_result['entries']
485 raise Exception('Invalid result type: %s' % result_type)
487 def select_format(self, format_spec, available_formats):
488 if format_spec == 'best' or format_spec is None:
489 return available_formats[-1]
490 elif format_spec == 'worst':
491 return available_formats[0]
493 extensions = [u'mp4', u'flv', u'webm', u'3gp']
494 if format_spec in extensions:
495 filter_f = lambda f: f['ext'] == format_spec
497 filter_f = lambda f: f['format_id'] == format_spec
498 matches = list(filter(filter_f, available_formats))
503 def process_video_result(self, info_dict, download=True):
504 assert info_dict.get('_type', 'video') == 'video'
506 if 'playlist' not in info_dict:
507 # It isn't part of a playlist
508 info_dict['playlist'] = None
509 info_dict['playlist_index'] = None
511 # This extractors handle format selection themselves
512 if info_dict['extractor'] in [u'youtube', u'Youku']:
514 self.process_info(info_dict)
517 # We now pick which formats have to be downloaded
518 if info_dict.get('formats') is None:
519 # There's only one format available
520 formats = [info_dict]
522 formats = info_dict['formats']
524 # We check that all the formats have the format and format_id fields
525 for (i, format) in enumerate(formats):
526 if format.get('format_id') is None:
527 format['format_id'] = compat_str(i)
528 if format.get('format') is None:
529 format['format'] = u'{id} - {res}{note}'.format(
530 id=format['format_id'],
531 res=self.format_resolution(format),
532 note=u' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '',
534 # Automatically determine file extension if missing
535 if 'ext' not in format:
536 format['ext'] = determine_ext(format['url'])
538 if self.params.get('listformats', None):
539 self.list_formats(info_dict)
542 format_limit = self.params.get('format_limit', None)
544 formats = list(takewhile_inclusive(
545 lambda f: f['format_id'] != format_limit, formats
547 if self.params.get('prefer_free_formats'):
548 def _free_formats_key(f):
550 ext_ord = [u'flv', u'mp4', u'webm'].index(f['ext'])
553 # We only compare the extension if they have the same height and width
554 return (f.get('height'), f.get('width'), ext_ord)
555 formats = sorted(formats, key=_free_formats_key)
557 req_format = self.params.get('format', 'best')
558 if req_format is None:
560 formats_to_download = []
561 # The -1 is for supporting YoutubeIE
562 if req_format in ('-1', 'all'):
563 formats_to_download = formats
565 # We can accept formats requestd in the format: 34/5/best, we pick
566 # the first that is available, starting from left
567 req_formats = req_format.split('/')
568 for rf in req_formats:
569 selected_format = self.select_format(rf, formats)
570 if selected_format is not None:
571 formats_to_download = [selected_format]
573 if not formats_to_download:
574 raise ExtractorError(u'requested format not available',
578 if len(formats_to_download) > 1:
579 self.to_screen(u'[info] %s: downloading video in %s formats' % (info_dict['id'], len(formats_to_download)))
580 for format in formats_to_download:
581 new_info = dict(info_dict)
582 new_info.update(format)
583 self.process_info(new_info)
584 # We update the info dict with the best quality format (backwards compatibility)
585 info_dict.update(formats_to_download[-1])
588 def process_info(self, info_dict):
589 """Process a single resolved IE result."""
591 assert info_dict.get('_type', 'video') == 'video'
592 #We increment the download the download count here to match the previous behaviour.
593 self.increment_downloads()
595 info_dict['fulltitle'] = info_dict['title']
596 if len(info_dict['title']) > 200:
597 info_dict['title'] = info_dict['title'][:197] + u'...'
599 # Keep for backwards compatibility
600 info_dict['stitle'] = info_dict['title']
602 if not 'format' in info_dict:
603 info_dict['format'] = info_dict['ext']
605 reason = self._match_entry(info_dict)
606 if reason is not None:
607 self.to_screen(u'[download] ' + reason)
610 max_downloads = self.params.get('max_downloads')
611 if max_downloads is not None:
612 if self._num_downloads > int(max_downloads):
613 raise MaxDownloadsReached()
615 filename = self.prepare_filename(info_dict)
618 if self.params.get('forcetitle', False):
619 compat_print(info_dict['title'])
620 if self.params.get('forceid', False):
621 compat_print(info_dict['id'])
622 if self.params.get('forceurl', False):
623 # For RTMP URLs, also include the playpath
624 compat_print(info_dict['url'] + info_dict.get('play_path', u''))
625 if self.params.get('forcethumbnail', False) and info_dict.get('thumbnail') is not None:
626 compat_print(info_dict['thumbnail'])
627 if self.params.get('forcedescription', False) and info_dict.get('description') is not None:
628 compat_print(info_dict['description'])
629 if self.params.get('forcefilename', False) and filename is not None:
630 compat_print(filename)
631 if self.params.get('forceformat', False):
632 compat_print(info_dict['format'])
634 # Do nothing else if in simulate mode
635 if self.params.get('simulate', False):
642 dn = os.path.dirname(encodeFilename(filename))
643 if dn != '' and not os.path.exists(dn):
645 except (OSError, IOError) as err:
646 self.report_error(u'unable to create directory ' + compat_str(err))
649 if self.params.get('writedescription', False):
651 descfn = filename + u'.description'
652 self.report_writedescription(descfn)
653 with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
654 descfile.write(info_dict['description'])
655 except (KeyError, TypeError):
656 self.report_warning(u'There\'s no description to write.')
657 except (OSError, IOError):
658 self.report_error(u'Cannot write description file ' + descfn)
661 if self.params.get('writeannotations', False):
663 annofn = filename + u'.annotations.xml'
664 self.report_writeannotations(annofn)
665 with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile:
666 annofile.write(info_dict['annotations'])
667 except (KeyError, TypeError):
668 self.report_warning(u'There are no annotations to write.')
669 except (OSError, IOError):
670 self.report_error(u'Cannot write annotations file: ' + annofn)
673 subtitles_are_requested = any([self.params.get('writesubtitles', False),
674 self.params.get('writeautomaticsub')])
676 if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:
677 # subtitles download errors are already managed as troubles in relevant IE
678 # that way it will silently go on when used with unsupporting IE
679 subtitles = info_dict['subtitles']
680 sub_format = self.params.get('subtitlesformat', 'srt')
681 for sub_lang in subtitles.keys():
682 sub = subtitles[sub_lang]
686 sub_filename = subtitles_filename(filename, sub_lang, sub_format)
687 self.report_writesubtitles(sub_filename)
688 with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
690 except (OSError, IOError):
691 self.report_error(u'Cannot write subtitles file ' + descfn)
694 if self.params.get('writeinfojson', False):
695 infofn = filename + u'.info.json'
696 self.report_writeinfojson(infofn)
698 json_info_dict = dict((k, v) for k, v in info_dict.items() if not k in ['urlhandle'])
699 write_json_file(json_info_dict, encodeFilename(infofn))
700 except (OSError, IOError):
701 self.report_error(u'Cannot write metadata to JSON file ' + infofn)
704 if self.params.get('writethumbnail', False):
705 if info_dict.get('thumbnail') is not None:
706 thumb_format = determine_ext(info_dict['thumbnail'], u'jpg')
707 thumb_filename = filename.rpartition('.')[0] + u'.' + thumb_format
708 self.to_screen(u'[%s] %s: Downloading thumbnail ...' %
709 (info_dict['extractor'], info_dict['id']))
711 uf = compat_urllib_request.urlopen(info_dict['thumbnail'])
712 with open(thumb_filename, 'wb') as thumbf:
713 shutil.copyfileobj(uf, thumbf)
714 self.to_screen(u'[%s] %s: Writing thumbnail to: %s' %
715 (info_dict['extractor'], info_dict['id'], thumb_filename))
716 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
717 self.report_warning(u'Unable to download thumbnail "%s": %s' %
718 (info_dict['thumbnail'], compat_str(err)))
720 if not self.params.get('skip_download', False):
721 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
725 success = self.fd._do_download(filename, info_dict)
726 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
727 self.report_error(u'unable to download video data: %s' % str(err))
729 except (OSError, IOError) as err:
730 raise UnavailableVideoError(err)
731 except (ContentTooShortError, ) as err:
732 self.report_error(u'content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
737 self.post_process(filename, info_dict)
738 except (PostProcessingError) as err:
739 self.report_error(u'postprocessing: %s' % str(err))
742 self.record_download_archive(info_dict)
744 def download(self, url_list):
745 """Download a given list of URLs."""
746 if len(url_list) > 1 and self.fixed_template():
747 raise SameFileError(self.params['outtmpl'])
751 #It also downloads the videos
752 videos = self.extract_info(url)
753 except UnavailableVideoError:
754 self.report_error(u'unable to download video')
755 except MaxDownloadsReached:
756 self.to_screen(u'[info] Maximum number of downloaded files reached.')
759 return self._download_retcode
761 def post_process(self, filename, ie_info):
762 """Run all the postprocessors on the given file."""
764 info['filepath'] = filename
768 keep_video_wish, new_info = pp.run(info)
769 if keep_video_wish is not None:
771 keep_video = keep_video_wish
772 elif keep_video is None:
773 # No clear decision yet, let IE decide
774 keep_video = keep_video_wish
775 except PostProcessingError as e:
776 self.report_error(e.msg)
777 if keep_video is False and not self.params.get('keepvideo', False):
779 self.to_screen(u'Deleting original file %s (pass -k to keep)' % filename)
780 os.remove(encodeFilename(filename))
781 except (IOError, OSError):
782 self.report_warning(u'Unable to remove downloaded video file')
784 def in_download_archive(self, info_dict):
785 fn = self.params.get('download_archive')
788 vid_id = info_dict['extractor'] + u' ' + info_dict['id']
790 with locked_file(fn, 'r', encoding='utf-8') as archive_file:
791 for line in archive_file:
792 if line.strip() == vid_id:
794 except IOError as ioe:
795 if ioe.errno != errno.ENOENT:
799 def record_download_archive(self, info_dict):
800 fn = self.params.get('download_archive')
803 vid_id = info_dict['extractor'] + u' ' + info_dict['id']
804 with locked_file(fn, 'a', encoding='utf-8') as archive_file:
805 archive_file.write(vid_id + u'\n')
808 def format_resolution(format, default='unknown'):
809 if format.get('_resolution') is not None:
810 return format['_resolution']
811 if format.get('height') is not None:
812 if format.get('width') is not None:
813 res = u'%sx%s' % (format['width'], format['height'])
815 res = u'%sp' % format['height']
820 def list_formats(self, info_dict):
821 def format_note(fdict):
822 if fdict.get('format_note') is not None:
823 return fdict['format_note']
825 if fdict.get('vcodec') is not None:
826 res += u'%-5s' % fdict['vcodec']
827 elif fdict.get('vbr') is not None:
829 if fdict.get('vbr') is not None:
830 res += u'@%4dk' % fdict['vbr']
831 if fdict.get('acodec') is not None:
834 res += u'%-5s' % fdict['acodec']
835 elif fdict.get('abr') is not None:
839 if fdict.get('abr') is not None:
840 res += u'@%3dk' % fdict['abr']
844 return (u'%-20s%-10s%-12s%s' % (
847 self.format_resolution(format),
852 formats = info_dict.get('formats', [info_dict])
853 formats_s = list(map(line, formats))
855 formats_s[0] += (' ' if format_note(formats[0]) else '') + '(worst)'
856 formats_s[-1] += (' ' if format_note(formats[-1]) else '') + '(best)'
859 'format_id': u'format code', 'ext': u'extension',
860 '_resolution': u'resolution', 'format_note': u'note'})
861 self.to_screen(u'[info] Available formats for %s:\n%s\n%s' %
862 (info_dict['id'], header_line, u"\n".join(formats_s)))