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