2 # -*- coding: utf-8 -*-
4 from __future__
import absolute_import
17 from .extractor
import get_info_extractor
, gen_extractors
18 from .FileDownloader
import FileDownloader
21 class YoutubeDL(object):
24 YoutubeDL objects are the ones responsible of downloading the
25 actual video file and writing it to disk if the user has requested
26 it, among some other tasks. In most cases there should be one per
27 program. As, given a video URL, the downloader doesn't know how to
28 extract all the needed information, task that InfoExtractors do, it
29 has to pass the URL to one of them.
31 For this, YoutubeDL objects have a method that allows
32 InfoExtractors to be registered in a given order. When it is passed
33 a URL, the YoutubeDL object handles it to the first InfoExtractor it
34 finds that reports being able to handle it. The InfoExtractor extracts
35 all the information about the video or videos the URL refers to, and
36 YoutubeDL process the extracted information, possibly using a File
37 Downloader to download the video.
39 YoutubeDL objects accept a lot of parameters. In order not to saturate
40 the object constructor with arguments, it receives a dictionary of
41 options instead. These options are available through the params
42 attribute for the InfoExtractors to use. The YoutubeDL also
43 registers itself as the downloader in charge for the InfoExtractors
44 that are added to it, so this is a "mutual registration".
48 username: Username for authentication purposes.
49 password: Password for authentication purposes.
50 videopassword: Password for acces a video.
51 usenetrc: Use netrc for authentication instead.
52 verbose: Print additional info to stdout.
53 quiet: Do not print messages to stdout.
54 forceurl: Force printing final URL.
55 forcetitle: Force printing title.
56 forceid: Force printing ID.
57 forcethumbnail: Force printing thumbnail URL.
58 forcedescription: Force printing description.
59 forcefilename: Force printing final filename.
60 simulate: Do not download the video files.
61 format: Video format code.
62 format_limit: Highest quality format to try.
63 outtmpl: Template for output names.
64 restrictfilenames: Do not allow "&" and spaces in file names
65 ignoreerrors: Do not stop on download errors.
66 nooverwrites: Prevent overwriting files.
67 playliststart: Playlist item to start at.
68 playlistend: Playlist item to end at.
69 matchtitle: Download only matching titles.
70 rejecttitle: Reject downloads for matching titles.
71 logtostderr: Log messages to stderr instead of stdout.
72 writedescription: Write the video description to a .description file
73 writeinfojson: Write the video description to a .info.json file
74 writeannotations: Write the video annotations to a .annotations.xml file
75 writethumbnail: Write the thumbnail image to a file
76 writesubtitles: Write the video subtitles to a file
77 writeautomaticsub: Write the automatic subtitles to a file
78 allsubtitles: Downloads all the subtitles of the video
79 (requires writesubtitles or writeautomaticsub)
80 listsubtitles: Lists all available subtitles for the video
81 subtitlesformat: Subtitle format [srt/sbv/vtt] (default=srt)
82 subtitleslangs: List of languages of the subtitles to download
83 keepvideo: Keep the video file after post-processing
84 daterange: A DateRange object, download only if the upload_date is in the range.
85 skip_download: Skip the actual download of the video file
86 cachedir: Location of the cache files in the filesystem.
87 None to disable filesystem cache.
88 noplaylist: Download single video instead of a playlist if in doubt.
89 age_limit: An integer representing the user's age in years.
90 Unsuitable videos for the given age are skipped.
91 downloadarchive: File name of a file where all downloads are recorded.
92 Videos already present in the file are not downloaded
95 The following parameters are not used by YoutubeDL itself, they are used by
97 nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
98 noresizebuffer, retries, continuedl, noprogress, consoletitle
104 _download_retcode
= None
105 _num_downloads
= None
108 def __init__(self
, params
):
109 """Create a FileDownloader object with the given options."""
111 self
._ies
_instances
= {}
113 self
._progress
_hooks
= []
114 self
._download
_retcode
= 0
115 self
._num
_downloads
= 0
116 self
._screen
_file
= [sys
.stdout
, sys
.stderr
][params
.get('logtostderr', False)]
118 if (sys
.version_info
>= (3,) and sys
.platform
!= 'win32' and
119 sys
.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968']
120 and not params
['restrictfilenames']):
121 # On Python 3, the Unicode filesystem API will throw errors (#1474)
123 u
'Assuming --restrict-filenames since file system encoding '
124 u
'cannot encode all charactes. '
125 u
'Set the LC_ALL environment variable to fix this.')
126 params
['restrictfilenames'] = True
129 self
.fd
= FileDownloader(self
, self
.params
)
131 if '%(stitle)s' in self
.params
['outtmpl']:
132 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.')
134 def add_info_extractor(self
, ie
):
135 """Add an InfoExtractor object to the end of the list."""
137 self
._ies
_instances
[ie
.ie_key()] = ie
138 ie
.set_downloader(self
)
140 def get_info_extractor(self
, ie_key
):
142 Get an instance of an IE with name ie_key, it will try to get one from
143 the _ies list, if there's no instance it will create a new one and add
144 it to the extractor list.
146 ie
= self
._ies
_instances
.get(ie_key
)
148 ie
= get_info_extractor(ie_key
)()
149 self
.add_info_extractor(ie
)
152 def add_default_info_extractors(self
):
154 Add the InfoExtractors returned by gen_extractors to the end of the list
156 for ie
in gen_extractors():
157 self
.add_info_extractor(ie
)
159 def add_post_processor(self
, pp
):
160 """Add a PostProcessor object to the end of the chain."""
162 pp
.set_downloader(self
)
164 def to_screen(self
, message
, skip_eol
=False):
165 """Print message to stdout if not in quiet mode."""
166 if not self
.params
.get('quiet', False):
167 terminator
= [u
'\n', u
''][skip_eol
]
168 output
= message
+ terminator
169 write_string(output
, self
._screen
_file
)
171 def to_stderr(self
, message
):
172 """Print message to stderr."""
173 assert type(message
) == type(u
'')
174 output
= message
+ u
'\n'
175 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
176 output
= output
.encode(preferredencoding())
177 sys
.stderr
.write(output
)
179 def fixed_template(self
):
180 """Checks if the output template is fixed."""
181 return (re
.search(u
'(?u)%\\(.+?\\)s', self
.params
['outtmpl']) is None)
183 def trouble(self
, message
=None, tb
=None):
184 """Determine action to take when a download problem appears.
186 Depending on if the downloader has been configured to ignore
187 download errors or not, this method may throw an exception or
188 not when errors are found, after printing the message.
190 tb, if given, is additional traceback information.
192 if message
is not None:
193 self
.to_stderr(message
)
194 if self
.params
.get('verbose'):
196 if sys
.exc_info()[0]: # if .trouble has been called from an except block
198 if hasattr(sys
.exc_info()[1], 'exc_info') and sys
.exc_info()[1].exc_info
[0]:
199 tb
+= u
''.join(traceback
.format_exception(*sys
.exc_info()[1].exc_info
))
200 tb
+= compat_str(traceback
.format_exc())
202 tb_data
= traceback
.format_list(traceback
.extract_stack())
203 tb
= u
''.join(tb_data
)
205 if not self
.params
.get('ignoreerrors', False):
206 if sys
.exc_info()[0] and hasattr(sys
.exc_info()[1], 'exc_info') and sys
.exc_info()[1].exc_info
[0]:
207 exc_info
= sys
.exc_info()[1].exc_info
209 exc_info
= sys
.exc_info()
210 raise DownloadError(message
, exc_info
)
211 self
._download
_retcode
= 1
213 def report_warning(self
, message
):
215 Print the message to stderr, it will be prefixed with 'WARNING:'
216 If stderr is a tty file the 'WARNING:' will be colored
218 if sys
.stderr
.isatty() and os
.name
!= 'nt':
219 _msg_header
= u
'\033[0;33mWARNING:\033[0m'
221 _msg_header
= u
'WARNING:'
222 warning_message
= u
'%s %s' % (_msg_header
, message
)
223 self
.to_stderr(warning_message
)
225 def report_error(self
, message
, tb
=None):
227 Do the same as trouble, but prefixes the message with 'ERROR:', colored
228 in red if stderr is a tty file.
230 if sys
.stderr
.isatty() and os
.name
!= 'nt':
231 _msg_header
= u
'\033[0;31mERROR:\033[0m'
233 _msg_header
= u
'ERROR:'
234 error_message
= u
'%s %s' % (_msg_header
, message
)
235 self
.trouble(error_message
, tb
)
237 def report_writedescription(self
, descfn
):
238 """ Report that the description file is being written """
239 self
.to_screen(u
'[info] Writing video description to: ' + descfn
)
241 def report_writesubtitles(self
, sub_filename
):
242 """ Report that the subtitles file is being written """
243 self
.to_screen(u
'[info] Writing video subtitles to: ' + sub_filename
)
245 def report_writeinfojson(self
, infofn
):
246 """ Report that the metadata file has been written """
247 self
.to_screen(u
'[info] Video description metadata as JSON to: ' + infofn
)
249 def report_writeannotations(self
, annofn
):
250 """ Report that the annotations file has been written. """
251 self
.to_screen(u
'[info] Writing video annotations to: ' + annofn
)
253 def report_file_already_downloaded(self
, file_name
):
254 """Report file has already been fully downloaded."""
256 self
.to_screen(u
'[download] %s has already been downloaded' % file_name
)
257 except (UnicodeEncodeError) as err
:
258 self
.to_screen(u
'[download] The file has already been downloaded')
260 def increment_downloads(self
):
261 """Increment the ordinal that assigns a number to each file."""
262 self
._num
_downloads
+= 1
264 def prepare_filename(self
, info_dict
):
265 """Generate the output filename."""
267 template_dict
= dict(info_dict
)
269 template_dict
['epoch'] = int(time
.time())
270 autonumber_size
= self
.params
.get('autonumber_size')
271 if autonumber_size
is None:
273 autonumber_templ
= u
'%0' + str(autonumber_size
) + u
'd'
274 template_dict
['autonumber'] = autonumber_templ
% self
._num
_downloads
275 if template_dict
.get('playlist_index') is not None:
276 template_dict
['playlist_index'] = u
'%05d' % template_dict
['playlist_index']
278 sanitize
= lambda k
, v
: sanitize_filename(
279 u
'NA' if v
is None else compat_str(v
),
280 restricted
=self
.params
.get('restrictfilenames'),
282 template_dict
= dict((k
, sanitize(k
, v
))
283 for k
, v
in template_dict
.items())
285 tmpl
= os
.path
.expanduser(self
.params
['outtmpl'])
286 filename
= tmpl
% template_dict
288 except KeyError as err
:
289 self
.report_error(u
'Erroneous output template')
291 except ValueError as err
:
292 self
.report_error(u
'Error in output template: ' + str(err
) + u
' (encoding: ' + repr(preferredencoding()) + ')')
295 def _match_entry(self
, info_dict
):
296 """ Returns None iff the file should be downloaded """
298 title
= info_dict
['title']
299 matchtitle
= self
.params
.get('matchtitle', False)
301 if not re
.search(matchtitle
, title
, re
.IGNORECASE
):
302 return u
'[download] "' + title
+ '" title did not match pattern "' + matchtitle
+ '"'
303 rejecttitle
= self
.params
.get('rejecttitle', False)
305 if re
.search(rejecttitle
, title
, re
.IGNORECASE
):
306 return u
'"' + title
+ '" title matched reject pattern "' + rejecttitle
+ '"'
307 date
= info_dict
.get('upload_date', None)
309 dateRange
= self
.params
.get('daterange', DateRange())
310 if date
not in dateRange
:
311 return u
'[download] %s upload date is not in range %s' % (date_from_str(date
).isoformat(), dateRange
)
312 age_limit
= self
.params
.get('age_limit')
313 if age_limit
is not None:
314 if age_limit
< info_dict
.get('age_limit', 0):
315 return u
'Skipping "' + title
+ '" because it is age restricted'
316 if self
.in_download_archive(info_dict
):
317 return (u
'%(title)s has already been recorded in archive'
322 def add_extra_info(info_dict
, extra_info
):
323 '''Set the keys from extra_info in info dict if they are missing'''
324 for key
, value
in extra_info
.items():
325 info_dict
.setdefault(key
, value
)
327 def extract_info(self
, url
, download
=True, ie_key
=None, extra_info
={}):
329 Returns a list with a dictionary for each video we find.
330 If 'download', also downloads the videos.
331 extra_info is a dict containing the extra values to add to each result
335 ies
= [self
.get_info_extractor(ie_key
)]
340 if not ie
.suitable(url
):
344 self
.report_warning(u
'The program functionality for this site has been marked as broken, '
345 u
'and will probably not work.')
348 ie_result
= ie
.extract(url
)
349 if ie_result
is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
351 if isinstance(ie_result
, list):
352 # Backwards compatibility: old IE result format
354 '_type': 'compat_list',
355 'entries': ie_result
,
357 if 'extractor' not in ie_result
:
358 ie_result
['extractor'] = ie
.IE_NAME
359 return self
.process_ie_result(ie_result
, download
, extra_info
)
360 except ExtractorError
as de
: # An error we somewhat expected
361 self
.report_error(compat_str(de
), de
.format_traceback())
363 except Exception as e
:
364 if self
.params
.get('ignoreerrors', False):
365 self
.report_error(compat_str(e
), tb
=compat_str(traceback
.format_exc()))
370 self
.report_error(u
'no suitable InfoExtractor: %s' % url
)
372 def process_ie_result(self
, ie_result
, download
=True, extra_info
={}):
374 Take the result of the ie(may be modified) and resolve all unresolved
375 references (URLs, playlist items).
377 It will also download the videos if 'download'.
378 Returns the resolved ie_result.
381 result_type
= ie_result
.get('_type', 'video') # If not given we suppose it's a video, support the default old system
382 if result_type
== 'video':
383 self
.add_extra_info(ie_result
, extra_info
)
384 return self
.process_video_result(ie_result
)
385 elif result_type
== 'url':
386 # We have to add extra_info to the results because it may be
387 # contained in a playlist
388 return self
.extract_info(ie_result
['url'],
390 ie_key
=ie_result
.get('ie_key'),
391 extra_info
=extra_info
)
392 elif result_type
== 'playlist':
393 self
.add_extra_info(ie_result
, extra_info
)
394 # We process each entry in the playlist
395 playlist
= ie_result
.get('title', None) or ie_result
.get('id', None)
396 self
.to_screen(u
'[download] Downloading playlist: %s' % playlist
)
398 playlist_results
= []
400 n_all_entries
= len(ie_result
['entries'])
401 playliststart
= self
.params
.get('playliststart', 1) - 1
402 playlistend
= self
.params
.get('playlistend', -1)
404 if playlistend
== -1:
405 entries
= ie_result
['entries'][playliststart
:]
407 entries
= ie_result
['entries'][playliststart
:playlistend
]
409 n_entries
= len(entries
)
411 self
.to_screen(u
"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
412 (ie_result
['extractor'], playlist
, n_all_entries
, n_entries
))
414 for i
, entry
in enumerate(entries
, 1):
415 self
.to_screen(u
'[download] Downloading video #%s of %s' % (i
, n_entries
))
417 'playlist': playlist
,
418 'playlist_index': i
+ playliststart
,
419 'extractor': ie_result
['extractor'],
421 entry_result
= self
.process_ie_result(entry
,
424 playlist_results
.append(entry_result
)
425 ie_result
['entries'] = playlist_results
427 elif result_type
== 'compat_list':
429 self
.add_extra_info(r
,
430 {'extractor': ie_result['extractor']}
)
432 ie_result
['entries'] = [
433 self
.process_ie_result(_fixup(r
), download
, extra_info
)
434 for r
in ie_result
['entries']
438 raise Exception('Invalid result type: %s' % result_type
)
440 def select_format(self
, format_spec
, available_formats
):
441 if format_spec
== 'best' or format_spec
is None:
442 return available_formats
[-1]
443 elif format_spec
== 'worst':
444 return available_formats
[0]
446 extensions
= [u
'mp4', u
'flv', u
'webm', u
'3gp']
447 if format_spec
in extensions
:
448 filter_f
= lambda f
: f
['ext'] == format_spec
450 filter_f
= lambda f
: f
['format_id'] == format_spec
451 matches
= list(filter(filter_f
, available_formats
))
456 def process_video_result(self
, info_dict
, download
=True):
457 assert info_dict
.get('_type', 'video') == 'video'
459 if 'playlist' not in info_dict
:
460 # It isn't part of a playlist
461 info_dict
['playlist'] = None
462 info_dict
['playlist_index'] = None
464 # This extractors handle format selection themselves
465 if info_dict
['extractor'] in [u
'youtube', u
'Youku']:
467 self
.process_info(info_dict
)
470 # We now pick which formats have to be downloaded
471 if info_dict
.get('formats') is None:
472 # There's only one format available
473 formats
= [info_dict
]
475 formats
= info_dict
['formats']
477 # We check that all the formats have the format and format_id fields
478 for (i
, format
) in enumerate(formats
):
479 if format
.get('format_id') is None:
480 format
['format_id'] = compat_str(i
)
481 if format
.get('format') is None:
482 format
['format'] = u
'{id} - {res}{note}'.format(
483 id=format
['format_id'],
484 res
=self
.format_resolution(format
),
485 note
=u
' ({0})'.format(format
['format_note']) if format
.get('format_note') is not None else '',
487 # Automatically determine file extension if missing
488 if 'ext' not in format
:
489 format
['ext'] = determine_ext(format
['url'])
491 if self
.params
.get('listformats', None):
492 self
.list_formats(info_dict
)
495 format_limit
= self
.params
.get('format_limit', None)
497 formats
= list(takewhile_inclusive(
498 lambda f
: f
['format_id'] != format_limit
, formats
500 if self
.params
.get('prefer_free_formats'):
501 def _free_formats_key(f
):
503 ext_ord
= [u
'flv', u
'mp4', u
'webm'].index(f
['ext'])
506 # We only compare the extension if they have the same height and width
507 return (f
.get('height'), f
.get('width'), ext_ord
)
508 formats
= sorted(formats
, key
=_free_formats_key
)
510 req_format
= self
.params
.get('format', 'best')
511 if req_format
is None:
513 formats_to_download
= []
514 # The -1 is for supporting YoutubeIE
515 if req_format
in ('-1', 'all'):
516 formats_to_download
= formats
518 # We can accept formats requestd in the format: 34/5/best, we pick
519 # the first that is available, starting from left
520 req_formats
= req_format
.split('/')
521 for rf
in req_formats
:
522 selected_format
= self
.select_format(rf
, formats
)
523 if selected_format
is not None:
524 formats_to_download
= [selected_format
]
526 if not formats_to_download
:
527 raise ExtractorError(u
'requested format not available',
531 if len(formats_to_download
) > 1:
532 self
.to_screen(u
'[info] %s: downloading video in %s formats' % (info_dict
['id'], len(formats_to_download
)))
533 for format
in formats_to_download
:
534 new_info
= dict(info_dict
)
535 new_info
.update(format
)
536 self
.process_info(new_info
)
537 # We update the info dict with the best quality format (backwards compatibility)
538 info_dict
.update(formats_to_download
[-1])
541 def process_info(self
, info_dict
):
542 """Process a single resolved IE result."""
544 assert info_dict
.get('_type', 'video') == 'video'
545 #We increment the download the download count here to match the previous behaviour.
546 self
.increment_downloads()
548 info_dict
['fulltitle'] = info_dict
['title']
549 if len(info_dict
['title']) > 200:
550 info_dict
['title'] = info_dict
['title'][:197] + u
'...'
552 # Keep for backwards compatibility
553 info_dict
['stitle'] = info_dict
['title']
555 if not 'format' in info_dict
:
556 info_dict
['format'] = info_dict
['ext']
558 reason
= self
._match
_entry
(info_dict
)
559 if reason
is not None:
560 self
.to_screen(u
'[download] ' + reason
)
563 max_downloads
= self
.params
.get('max_downloads')
564 if max_downloads
is not None:
565 if self
._num
_downloads
> int(max_downloads
):
566 raise MaxDownloadsReached()
568 filename
= self
.prepare_filename(info_dict
)
571 if self
.params
.get('forcetitle', False):
572 compat_print(info_dict
['title'])
573 if self
.params
.get('forceid', False):
574 compat_print(info_dict
['id'])
575 if self
.params
.get('forceurl', False):
576 # For RTMP URLs, also include the playpath
577 compat_print(info_dict
['url'] + info_dict
.get('play_path', u
''))
578 if self
.params
.get('forcethumbnail', False) and info_dict
.get('thumbnail') is not None:
579 compat_print(info_dict
['thumbnail'])
580 if self
.params
.get('forcedescription', False) and info_dict
.get('description') is not None:
581 compat_print(info_dict
['description'])
582 if self
.params
.get('forcefilename', False) and filename
is not None:
583 compat_print(filename
)
584 if self
.params
.get('forceformat', False):
585 compat_print(info_dict
['format'])
587 # Do nothing else if in simulate mode
588 if self
.params
.get('simulate', False):
595 dn
= os
.path
.dirname(encodeFilename(filename
))
596 if dn
!= '' and not os
.path
.exists(dn
):
598 except (OSError, IOError) as err
:
599 self
.report_error(u
'unable to create directory ' + compat_str(err
))
602 if self
.params
.get('writedescription', False):
604 descfn
= filename
+ u
'.description'
605 self
.report_writedescription(descfn
)
606 with io
.open(encodeFilename(descfn
), 'w', encoding
='utf-8') as descfile
:
607 descfile
.write(info_dict
['description'])
608 except (KeyError, TypeError):
609 self
.report_warning(u
'There\'s no description to write.')
610 except (OSError, IOError):
611 self
.report_error(u
'Cannot write description file ' + descfn
)
614 if self
.params
.get('writeannotations', False):
616 annofn
= filename
+ u
'.annotations.xml'
617 self
.report_writeannotations(annofn
)
618 with io
.open(encodeFilename(annofn
), 'w', encoding
='utf-8') as annofile
:
619 annofile
.write(info_dict
['annotations'])
620 except (KeyError, TypeError):
621 self
.report_warning(u
'There are no annotations to write.')
622 except (OSError, IOError):
623 self
.report_error(u
'Cannot write annotations file: ' + annofn
)
626 subtitles_are_requested
= any([self
.params
.get('writesubtitles', False),
627 self
.params
.get('writeautomaticsub')])
629 if subtitles_are_requested
and 'subtitles' in info_dict
and info_dict
['subtitles']:
630 # subtitles download errors are already managed as troubles in relevant IE
631 # that way it will silently go on when used with unsupporting IE
632 subtitles
= info_dict
['subtitles']
633 sub_format
= self
.params
.get('subtitlesformat')
634 for sub_lang
in subtitles
.keys():
635 sub
= subtitles
[sub_lang
]
639 sub_filename
= subtitles_filename(filename
, sub_lang
, sub_format
)
640 self
.report_writesubtitles(sub_filename
)
641 with io
.open(encodeFilename(sub_filename
), 'w', encoding
='utf-8') as subfile
:
643 except (OSError, IOError):
644 self
.report_error(u
'Cannot write subtitles file ' + descfn
)
647 if self
.params
.get('writeinfojson', False):
648 infofn
= filename
+ u
'.info.json'
649 self
.report_writeinfojson(infofn
)
651 json_info_dict
= dict((k
, v
) for k
, v
in info_dict
.items() if not k
in ['urlhandle'])
652 write_json_file(json_info_dict
, encodeFilename(infofn
))
653 except (OSError, IOError):
654 self
.report_error(u
'Cannot write metadata to JSON file ' + infofn
)
657 if self
.params
.get('writethumbnail', False):
658 if info_dict
.get('thumbnail') is not None:
659 thumb_format
= determine_ext(info_dict
['thumbnail'], u
'jpg')
660 thumb_filename
= filename
.rpartition('.')[0] + u
'.' + thumb_format
661 self
.to_screen(u
'[%s] %s: Downloading thumbnail ...' %
662 (info_dict
['extractor'], info_dict
['id']))
664 uf
= compat_urllib_request
.urlopen(info_dict
['thumbnail'])
665 with open(thumb_filename
, 'wb') as thumbf
:
666 shutil
.copyfileobj(uf
, thumbf
)
667 self
.to_screen(u
'[%s] %s: Writing thumbnail to: %s' %
668 (info_dict
['extractor'], info_dict
['id'], thumb_filename
))
669 except (compat_urllib_error
.URLError
, compat_http_client
.HTTPException
, socket
.error
) as err
:
670 self
.report_warning(u
'Unable to download thumbnail "%s": %s' %
671 (info_dict
['thumbnail'], compat_str(err
)))
673 if not self
.params
.get('skip_download', False):
674 if self
.params
.get('nooverwrites', False) and os
.path
.exists(encodeFilename(filename
)):
678 success
= self
.fd
._do
_download
(filename
, info_dict
)
679 except (compat_urllib_error
.URLError
, compat_http_client
.HTTPException
, socket
.error
) as err
:
680 self
.report_error(u
'unable to download video data: %s' % str(err
))
682 except (OSError, IOError) as err
:
683 raise UnavailableVideoError(err
)
684 except (ContentTooShortError
, ) as err
:
685 self
.report_error(u
'content too short (expected %s bytes and served %s)' % (err
.expected
, err
.downloaded
))
690 self
.post_process(filename
, info_dict
)
691 except (PostProcessingError
) as err
:
692 self
.report_error(u
'postprocessing: %s' % str(err
))
695 self
.record_download_archive(info_dict
)
697 def download(self
, url_list
):
698 """Download a given list of URLs."""
699 if len(url_list
) > 1 and self
.fixed_template():
700 raise SameFileError(self
.params
['outtmpl'])
704 #It also downloads the videos
705 videos
= self
.extract_info(url
)
706 except UnavailableVideoError
:
707 self
.report_error(u
'unable to download video')
708 except MaxDownloadsReached
:
709 self
.to_screen(u
'[info] Maximum number of downloaded files reached.')
712 return self
._download
_retcode
714 def post_process(self
, filename
, ie_info
):
715 """Run all the postprocessors on the given file."""
717 info
['filepath'] = filename
721 keep_video_wish
, new_info
= pp
.run(info
)
722 if keep_video_wish
is not None:
724 keep_video
= keep_video_wish
725 elif keep_video
is None:
726 # No clear decision yet, let IE decide
727 keep_video
= keep_video_wish
728 except PostProcessingError
as e
:
729 self
.report_error(e
.msg
)
730 if keep_video
is False and not self
.params
.get('keepvideo', False):
732 self
.to_screen(u
'Deleting original file %s (pass -k to keep)' % filename
)
733 os
.remove(encodeFilename(filename
))
734 except (IOError, OSError):
735 self
.report_warning(u
'Unable to remove downloaded video file')
737 def in_download_archive(self
, info_dict
):
738 fn
= self
.params
.get('download_archive')
741 vid_id
= info_dict
['extractor'] + u
' ' + info_dict
['id']
743 with locked_file(fn
, 'r', encoding
='utf-8') as archive_file
:
744 for line
in archive_file
:
745 if line
.strip() == vid_id
:
747 except IOError as ioe
:
748 if ioe
.errno
!= errno
.ENOENT
:
752 def record_download_archive(self
, info_dict
):
753 fn
= self
.params
.get('download_archive')
756 vid_id
= info_dict
['extractor'] + u
' ' + info_dict
['id']
757 with locked_file(fn
, 'a', encoding
='utf-8') as archive_file
:
758 archive_file
.write(vid_id
+ u
'\n')
761 def format_resolution(format
, default
='unknown'):
762 if format
.get('_resolution') is not None:
763 return format
['_resolution']
764 if format
.get('height') is not None:
765 if format
.get('width') is not None:
766 res
= u
'%sx%s' % (format
['width'], format
['height'])
768 res
= u
'%sp' % format
['height']
773 def list_formats(self
, info_dict
):
775 return (u
'%-15s%-10s%-12s%s' % (
778 self
.format_resolution(format
),
779 format
.get('format_note', ''),
783 formats
= info_dict
.get('formats', [info_dict
])
784 formats_s
= list(map(line
, formats
))
786 formats_s
[0] += (' ' if formats
[0].get('format_note') else '') + '(worst)'
787 formats_s
[-1] += (' ' if formats
[-1].get('format_note') else '') + '(best)'
790 'format_id': u
'format code', 'ext': u
'extension',
791 '_resolution': u
'resolution', 'format_note': u
'note'})
792 self
.to_screen(u
'[info] Available formats for %s:\n%s\n%s' %
793 (info_dict
['id'], header_line
, u
"\n".join(formats_s
)))