]> Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/YoutubeDL.py
Imported Upstream version 2014.11.21
[youtubedl] / youtube_dl / YoutubeDL.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from __future__ import absolute_import, unicode_literals
5
6 import collections
7 import datetime
8 import errno
9 import io
10 import json
11 import locale
12 import os
13 import platform
14 import re
15 import shutil
16 import subprocess
17 import socket
18 import sys
19 import time
20 import traceback
21
22 if os.name == 'nt':
23 import ctypes
24
25 from .compat import (
26 compat_cookiejar,
27 compat_expanduser,
28 compat_http_client,
29 compat_str,
30 compat_urllib_error,
31 compat_urllib_request,
32 )
33 from .utils import (
34 escape_url,
35 ContentTooShortError,
36 date_from_str,
37 DateRange,
38 DEFAULT_OUTTMPL,
39 determine_ext,
40 DownloadError,
41 encodeFilename,
42 ExtractorError,
43 format_bytes,
44 formatSeconds,
45 get_term_width,
46 locked_file,
47 make_HTTPS_handler,
48 MaxDownloadsReached,
49 PagedList,
50 PostProcessingError,
51 platform_name,
52 preferredencoding,
53 SameFileError,
54 sanitize_filename,
55 subtitles_filename,
56 takewhile_inclusive,
57 UnavailableVideoError,
58 url_basename,
59 write_json_file,
60 write_string,
61 YoutubeDLHandler,
62 prepend_extension,
63 )
64 from .cache import Cache
65 from .extractor import get_info_extractor, gen_extractors
66 from .downloader import get_suitable_downloader
67 from .downloader.rtmp import rtmpdump_version
68 from .postprocessor import FFmpegMergerPP, FFmpegPostProcessor
69 from .version import __version__
70
71
72 class YoutubeDL(object):
73 """YoutubeDL class.
74
75 YoutubeDL objects are the ones responsible of downloading the
76 actual video file and writing it to disk if the user has requested
77 it, among some other tasks. In most cases there should be one per
78 program. As, given a video URL, the downloader doesn't know how to
79 extract all the needed information, task that InfoExtractors do, it
80 has to pass the URL to one of them.
81
82 For this, YoutubeDL objects have a method that allows
83 InfoExtractors to be registered in a given order. When it is passed
84 a URL, the YoutubeDL object handles it to the first InfoExtractor it
85 finds that reports being able to handle it. The InfoExtractor extracts
86 all the information about the video or videos the URL refers to, and
87 YoutubeDL process the extracted information, possibly using a File
88 Downloader to download the video.
89
90 YoutubeDL objects accept a lot of parameters. In order not to saturate
91 the object constructor with arguments, it receives a dictionary of
92 options instead. These options are available through the params
93 attribute for the InfoExtractors to use. The YoutubeDL also
94 registers itself as the downloader in charge for the InfoExtractors
95 that are added to it, so this is a "mutual registration".
96
97 Available options:
98
99 username: Username for authentication purposes.
100 password: Password for authentication purposes.
101 videopassword: Password for acces a video.
102 usenetrc: Use netrc for authentication instead.
103 verbose: Print additional info to stdout.
104 quiet: Do not print messages to stdout.
105 no_warnings: Do not print out anything for warnings.
106 forceurl: Force printing final URL.
107 forcetitle: Force printing title.
108 forceid: Force printing ID.
109 forcethumbnail: Force printing thumbnail URL.
110 forcedescription: Force printing description.
111 forcefilename: Force printing final filename.
112 forceduration: Force printing duration.
113 forcejson: Force printing info_dict as JSON.
114 dump_single_json: Force printing the info_dict of the whole playlist
115 (or video) as a single JSON line.
116 simulate: Do not download the video files.
117 format: Video format code.
118 format_limit: Highest quality format to try.
119 outtmpl: Template for output names.
120 restrictfilenames: Do not allow "&" and spaces in file names
121 ignoreerrors: Do not stop on download errors.
122 nooverwrites: Prevent overwriting files.
123 playliststart: Playlist item to start at.
124 playlistend: Playlist item to end at.
125 matchtitle: Download only matching titles.
126 rejecttitle: Reject downloads for matching titles.
127 logger: Log messages to a logging.Logger instance.
128 logtostderr: Log messages to stderr instead of stdout.
129 writedescription: Write the video description to a .description file
130 writeinfojson: Write the video description to a .info.json file
131 writeannotations: Write the video annotations to a .annotations.xml file
132 writethumbnail: Write the thumbnail image to a file
133 writesubtitles: Write the video subtitles to a file
134 writeautomaticsub: Write the automatic subtitles to a file
135 allsubtitles: Downloads all the subtitles of the video
136 (requires writesubtitles or writeautomaticsub)
137 listsubtitles: Lists all available subtitles for the video
138 subtitlesformat: Subtitle format [srt/sbv/vtt] (default=srt)
139 subtitleslangs: List of languages of the subtitles to download
140 keepvideo: Keep the video file after post-processing
141 daterange: A DateRange object, download only if the upload_date is in the range.
142 skip_download: Skip the actual download of the video file
143 cachedir: Location of the cache files in the filesystem.
144 False to disable filesystem cache.
145 noplaylist: Download single video instead of a playlist if in doubt.
146 age_limit: An integer representing the user's age in years.
147 Unsuitable videos for the given age are skipped.
148 min_views: An integer representing the minimum view count the video
149 must have in order to not be skipped.
150 Videos without view count information are always
151 downloaded. None for no limit.
152 max_views: An integer representing the maximum view count.
153 Videos that are more popular than that are not
154 downloaded.
155 Videos without view count information are always
156 downloaded. None for no limit.
157 download_archive: File name of a file where all downloads are recorded.
158 Videos already present in the file are not downloaded
159 again.
160 cookiefile: File name where cookies should be read from and dumped to.
161 nocheckcertificate:Do not verify SSL certificates
162 prefer_insecure: Use HTTP instead of HTTPS to retrieve information.
163 At the moment, this is only supported by YouTube.
164 proxy: URL of the proxy server to use
165 socket_timeout: Time to wait for unresponsive hosts, in seconds
166 bidi_workaround: Work around buggy terminals without bidirectional text
167 support, using fridibi
168 debug_printtraffic:Print out sent and received HTTP traffic
169 include_ads: Download ads as well
170 default_search: Prepend this string if an input url is not valid.
171 'auto' for elaborate guessing
172 encoding: Use this encoding instead of the system-specified.
173 extract_flat: Do not resolve URLs, return the immediate result.
174 Pass in 'in_playlist' to only show this behavior for
175 playlist items.
176
177 The following parameters are not used by YoutubeDL itself, they are used by
178 the FileDownloader:
179 nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
180 noresizebuffer, retries, continuedl, noprogress, consoletitle
181
182 The following options are used by the post processors:
183 prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
184 otherwise prefer avconv.
185 exec_cmd: Arbitrary command to run after downloading
186 """
187
188 params = None
189 _ies = []
190 _pps = []
191 _download_retcode = None
192 _num_downloads = None
193 _screen_file = None
194
195 def __init__(self, params=None, auto_init=True):
196 """Create a FileDownloader object with the given options."""
197 if params is None:
198 params = {}
199 self._ies = []
200 self._ies_instances = {}
201 self._pps = []
202 self._progress_hooks = []
203 self._download_retcode = 0
204 self._num_downloads = 0
205 self._screen_file = [sys.stdout, sys.stderr][params.get('logtostderr', False)]
206 self._err_file = sys.stderr
207 self.params = params
208 self.cache = Cache(self)
209
210 if params.get('bidi_workaround', False):
211 try:
212 import pty
213 master, slave = pty.openpty()
214 width = get_term_width()
215 if width is None:
216 width_args = []
217 else:
218 width_args = ['-w', str(width)]
219 sp_kwargs = dict(
220 stdin=subprocess.PIPE,
221 stdout=slave,
222 stderr=self._err_file)
223 try:
224 self._output_process = subprocess.Popen(
225 ['bidiv'] + width_args, **sp_kwargs
226 )
227 except OSError:
228 self._output_process = subprocess.Popen(
229 ['fribidi', '-c', 'UTF-8'] + width_args, **sp_kwargs)
230 self._output_channel = os.fdopen(master, 'rb')
231 except OSError as ose:
232 if ose.errno == 2:
233 self.report_warning('Could not find fribidi executable, ignoring --bidi-workaround . Make sure that fribidi is an executable file in one of the directories in your $PATH.')
234 else:
235 raise
236
237 if (sys.version_info >= (3,) and sys.platform != 'win32' and
238 sys.getfilesystemencoding() in ['ascii', 'ANSI_X3.4-1968']
239 and not params.get('restrictfilenames', False)):
240 # On Python 3, the Unicode filesystem API will throw errors (#1474)
241 self.report_warning(
242 'Assuming --restrict-filenames since file system encoding '
243 'cannot encode all characters. '
244 'Set the LC_ALL environment variable to fix this.')
245 self.params['restrictfilenames'] = True
246
247 if '%(stitle)s' in self.params.get('outtmpl', ''):
248 self.report_warning('%(stitle)s is deprecated. Use the %(title)s and the --restrict-filenames flag(which also secures %(uploader)s et al) instead.')
249
250 self._setup_opener()
251
252 if auto_init:
253 self.print_debug_header()
254 self.add_default_info_extractors()
255
256 def add_info_extractor(self, ie):
257 """Add an InfoExtractor object to the end of the list."""
258 self._ies.append(ie)
259 self._ies_instances[ie.ie_key()] = ie
260 ie.set_downloader(self)
261
262 def get_info_extractor(self, ie_key):
263 """
264 Get an instance of an IE with name ie_key, it will try to get one from
265 the _ies list, if there's no instance it will create a new one and add
266 it to the extractor list.
267 """
268 ie = self._ies_instances.get(ie_key)
269 if ie is None:
270 ie = get_info_extractor(ie_key)()
271 self.add_info_extractor(ie)
272 return ie
273
274 def add_default_info_extractors(self):
275 """
276 Add the InfoExtractors returned by gen_extractors to the end of the list
277 """
278 for ie in gen_extractors():
279 self.add_info_extractor(ie)
280
281 def add_post_processor(self, pp):
282 """Add a PostProcessor object to the end of the chain."""
283 self._pps.append(pp)
284 pp.set_downloader(self)
285
286 def add_progress_hook(self, ph):
287 """Add the progress hook (currently only for the file downloader)"""
288 self._progress_hooks.append(ph)
289
290 def _bidi_workaround(self, message):
291 if not hasattr(self, '_output_channel'):
292 return message
293
294 assert hasattr(self, '_output_process')
295 assert isinstance(message, compat_str)
296 line_count = message.count('\n') + 1
297 self._output_process.stdin.write((message + '\n').encode('utf-8'))
298 self._output_process.stdin.flush()
299 res = ''.join(self._output_channel.readline().decode('utf-8')
300 for _ in range(line_count))
301 return res[:-len('\n')]
302
303 def to_screen(self, message, skip_eol=False):
304 """Print message to stdout if not in quiet mode."""
305 return self.to_stdout(message, skip_eol, check_quiet=True)
306
307 def _write_string(self, s, out=None):
308 write_string(s, out=out, encoding=self.params.get('encoding'))
309
310 def to_stdout(self, message, skip_eol=False, check_quiet=False):
311 """Print message to stdout if not in quiet mode."""
312 if self.params.get('logger'):
313 self.params['logger'].debug(message)
314 elif not check_quiet or not self.params.get('quiet', False):
315 message = self._bidi_workaround(message)
316 terminator = ['\n', ''][skip_eol]
317 output = message + terminator
318
319 self._write_string(output, self._screen_file)
320
321 def to_stderr(self, message):
322 """Print message to stderr."""
323 assert isinstance(message, compat_str)
324 if self.params.get('logger'):
325 self.params['logger'].error(message)
326 else:
327 message = self._bidi_workaround(message)
328 output = message + '\n'
329 self._write_string(output, self._err_file)
330
331 def to_console_title(self, message):
332 if not self.params.get('consoletitle', False):
333 return
334 if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
335 # c_wchar_p() might not be necessary if `message` is
336 # already of type unicode()
337 ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
338 elif 'TERM' in os.environ:
339 self._write_string('\033]0;%s\007' % message, self._screen_file)
340
341 def save_console_title(self):
342 if not self.params.get('consoletitle', False):
343 return
344 if 'TERM' in os.environ:
345 # Save the title on stack
346 self._write_string('\033[22;0t', self._screen_file)
347
348 def restore_console_title(self):
349 if not self.params.get('consoletitle', False):
350 return
351 if 'TERM' in os.environ:
352 # Restore the title from stack
353 self._write_string('\033[23;0t', self._screen_file)
354
355 def __enter__(self):
356 self.save_console_title()
357 return self
358
359 def __exit__(self, *args):
360 self.restore_console_title()
361
362 if self.params.get('cookiefile') is not None:
363 self.cookiejar.save()
364
365 def trouble(self, message=None, tb=None):
366 """Determine action to take when a download problem appears.
367
368 Depending on if the downloader has been configured to ignore
369 download errors or not, this method may throw an exception or
370 not when errors are found, after printing the message.
371
372 tb, if given, is additional traceback information.
373 """
374 if message is not None:
375 self.to_stderr(message)
376 if self.params.get('verbose'):
377 if tb is None:
378 if sys.exc_info()[0]: # if .trouble has been called from an except block
379 tb = ''
380 if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
381 tb += ''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
382 tb += compat_str(traceback.format_exc())
383 else:
384 tb_data = traceback.format_list(traceback.extract_stack())
385 tb = ''.join(tb_data)
386 self.to_stderr(tb)
387 if not self.params.get('ignoreerrors', False):
388 if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
389 exc_info = sys.exc_info()[1].exc_info
390 else:
391 exc_info = sys.exc_info()
392 raise DownloadError(message, exc_info)
393 self._download_retcode = 1
394
395 def report_warning(self, message):
396 '''
397 Print the message to stderr, it will be prefixed with 'WARNING:'
398 If stderr is a tty file the 'WARNING:' will be colored
399 '''
400 if self.params.get('logger') is not None:
401 self.params['logger'].warning(message)
402 else:
403 if self.params.get('no_warnings'):
404 return
405 if self._err_file.isatty() and os.name != 'nt':
406 _msg_header = '\033[0;33mWARNING:\033[0m'
407 else:
408 _msg_header = 'WARNING:'
409 warning_message = '%s %s' % (_msg_header, message)
410 self.to_stderr(warning_message)
411
412 def report_error(self, message, tb=None):
413 '''
414 Do the same as trouble, but prefixes the message with 'ERROR:', colored
415 in red if stderr is a tty file.
416 '''
417 if self._err_file.isatty() and os.name != 'nt':
418 _msg_header = '\033[0;31mERROR:\033[0m'
419 else:
420 _msg_header = 'ERROR:'
421 error_message = '%s %s' % (_msg_header, message)
422 self.trouble(error_message, tb)
423
424 def report_file_already_downloaded(self, file_name):
425 """Report file has already been fully downloaded."""
426 try:
427 self.to_screen('[download] %s has already been downloaded' % file_name)
428 except UnicodeEncodeError:
429 self.to_screen('[download] The file has already been downloaded')
430
431 def prepare_filename(self, info_dict):
432 """Generate the output filename."""
433 try:
434 template_dict = dict(info_dict)
435
436 template_dict['epoch'] = int(time.time())
437 autonumber_size = self.params.get('autonumber_size')
438 if autonumber_size is None:
439 autonumber_size = 5
440 autonumber_templ = '%0' + str(autonumber_size) + 'd'
441 template_dict['autonumber'] = autonumber_templ % self._num_downloads
442 if template_dict.get('playlist_index') is not None:
443 template_dict['playlist_index'] = '%0*d' % (len(str(template_dict['n_entries'])), template_dict['playlist_index'])
444 if template_dict.get('resolution') is None:
445 if template_dict.get('width') and template_dict.get('height'):
446 template_dict['resolution'] = '%dx%d' % (template_dict['width'], template_dict['height'])
447 elif template_dict.get('height'):
448 template_dict['resolution'] = '%sp' % template_dict['height']
449 elif template_dict.get('width'):
450 template_dict['resolution'] = '?x%d' % template_dict['width']
451
452 sanitize = lambda k, v: sanitize_filename(
453 compat_str(v),
454 restricted=self.params.get('restrictfilenames'),
455 is_id=(k == 'id'))
456 template_dict = dict((k, sanitize(k, v))
457 for k, v in template_dict.items()
458 if v is not None)
459 template_dict = collections.defaultdict(lambda: 'NA', template_dict)
460
461 outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
462 tmpl = compat_expanduser(outtmpl)
463 filename = tmpl % template_dict
464 return filename
465 except ValueError as err:
466 self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
467 return None
468
469 def _match_entry(self, info_dict):
470 """ Returns None iff the file should be downloaded """
471
472 video_title = info_dict.get('title', info_dict.get('id', 'video'))
473 if 'title' in info_dict:
474 # This can happen when we're just evaluating the playlist
475 title = info_dict['title']
476 matchtitle = self.params.get('matchtitle', False)
477 if matchtitle:
478 if not re.search(matchtitle, title, re.IGNORECASE):
479 return '"' + title + '" title did not match pattern "' + matchtitle + '"'
480 rejecttitle = self.params.get('rejecttitle', False)
481 if rejecttitle:
482 if re.search(rejecttitle, title, re.IGNORECASE):
483 return '"' + title + '" title matched reject pattern "' + rejecttitle + '"'
484 date = info_dict.get('upload_date', None)
485 if date is not None:
486 dateRange = self.params.get('daterange', DateRange())
487 if date not in dateRange:
488 return '%s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
489 view_count = info_dict.get('view_count', None)
490 if view_count is not None:
491 min_views = self.params.get('min_views')
492 if min_views is not None and view_count < min_views:
493 return 'Skipping %s, because it has not reached minimum view count (%d/%d)' % (video_title, view_count, min_views)
494 max_views = self.params.get('max_views')
495 if max_views is not None and view_count > max_views:
496 return 'Skipping %s, because it has exceeded the maximum view count (%d/%d)' % (video_title, view_count, max_views)
497 age_limit = self.params.get('age_limit')
498 if age_limit is not None:
499 actual_age_limit = info_dict.get('age_limit')
500 if actual_age_limit is None:
501 actual_age_limit = 0
502 if age_limit < actual_age_limit:
503 return 'Skipping "' + title + '" because it is age restricted'
504 if self.in_download_archive(info_dict):
505 return '%s has already been recorded in archive' % video_title
506 return None
507
508 @staticmethod
509 def add_extra_info(info_dict, extra_info):
510 '''Set the keys from extra_info in info dict if they are missing'''
511 for key, value in extra_info.items():
512 info_dict.setdefault(key, value)
513
514 def extract_info(self, url, download=True, ie_key=None, extra_info={},
515 process=True):
516 '''
517 Returns a list with a dictionary for each video we find.
518 If 'download', also downloads the videos.
519 extra_info is a dict containing the extra values to add to each result
520 '''
521
522 if ie_key:
523 ies = [self.get_info_extractor(ie_key)]
524 else:
525 ies = self._ies
526
527 for ie in ies:
528 if not ie.suitable(url):
529 continue
530
531 if not ie.working():
532 self.report_warning('The program functionality for this site has been marked as broken, '
533 'and will probably not work.')
534
535 try:
536 ie_result = ie.extract(url)
537 if ie_result is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
538 break
539 if isinstance(ie_result, list):
540 # Backwards compatibility: old IE result format
541 ie_result = {
542 '_type': 'compat_list',
543 'entries': ie_result,
544 }
545 self.add_default_extra_info(ie_result, ie, url)
546 if process:
547 return self.process_ie_result(ie_result, download, extra_info)
548 else:
549 return ie_result
550 except ExtractorError as de: # An error we somewhat expected
551 self.report_error(compat_str(de), de.format_traceback())
552 break
553 except MaxDownloadsReached:
554 raise
555 except Exception as e:
556 if self.params.get('ignoreerrors', False):
557 self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
558 break
559 else:
560 raise
561 else:
562 self.report_error('no suitable InfoExtractor for URL %s' % url)
563
564 def add_default_extra_info(self, ie_result, ie, url):
565 self.add_extra_info(ie_result, {
566 'extractor': ie.IE_NAME,
567 'webpage_url': url,
568 'webpage_url_basename': url_basename(url),
569 'extractor_key': ie.ie_key(),
570 })
571
572 def process_ie_result(self, ie_result, download=True, extra_info={}):
573 """
574 Take the result of the ie(may be modified) and resolve all unresolved
575 references (URLs, playlist items).
576
577 It will also download the videos if 'download'.
578 Returns the resolved ie_result.
579 """
580
581 result_type = ie_result.get('_type', 'video')
582
583 if result_type in ('url', 'url_transparent'):
584 extract_flat = self.params.get('extract_flat', False)
585 if ((extract_flat == 'in_playlist' and 'playlist' in extra_info) or
586 extract_flat is True):
587 if self.params.get('forcejson', False):
588 self.to_stdout(json.dumps(ie_result))
589 return ie_result
590
591 if result_type == 'video':
592 self.add_extra_info(ie_result, extra_info)
593 return self.process_video_result(ie_result, download=download)
594 elif result_type == 'url':
595 # We have to add extra_info to the results because it may be
596 # contained in a playlist
597 return self.extract_info(ie_result['url'],
598 download,
599 ie_key=ie_result.get('ie_key'),
600 extra_info=extra_info)
601 elif result_type == 'url_transparent':
602 # Use the information from the embedding page
603 info = self.extract_info(
604 ie_result['url'], ie_key=ie_result.get('ie_key'),
605 extra_info=extra_info, download=False, process=False)
606
607 def make_result(embedded_info):
608 new_result = ie_result.copy()
609 for f in ('_type', 'url', 'ext', 'player_url', 'formats',
610 'entries', 'ie_key', 'duration',
611 'subtitles', 'annotations', 'format',
612 'thumbnail', 'thumbnails'):
613 if f in new_result:
614 del new_result[f]
615 if f in embedded_info:
616 new_result[f] = embedded_info[f]
617 return new_result
618 new_result = make_result(info)
619
620 assert new_result.get('_type') != 'url_transparent'
621 if new_result.get('_type') == 'compat_list':
622 new_result['entries'] = [
623 make_result(e) for e in new_result['entries']]
624
625 return self.process_ie_result(
626 new_result, download=download, extra_info=extra_info)
627 elif result_type == 'playlist' or playlist == 'multi_video':
628 # We process each entry in the playlist
629 playlist = ie_result.get('title', None) or ie_result.get('id', None)
630 self.to_screen('[download] Downloading playlist: %s' % playlist)
631
632 playlist_results = []
633
634 playliststart = self.params.get('playliststart', 1) - 1
635 playlistend = self.params.get('playlistend', None)
636 # For backwards compatibility, interpret -1 as whole list
637 if playlistend == -1:
638 playlistend = None
639
640 if isinstance(ie_result['entries'], list):
641 n_all_entries = len(ie_result['entries'])
642 entries = ie_result['entries'][playliststart:playlistend]
643 n_entries = len(entries)
644 self.to_screen(
645 "[%s] playlist %s: Collected %d video ids (downloading %d of them)" %
646 (ie_result['extractor'], playlist, n_all_entries, n_entries))
647 else:
648 assert isinstance(ie_result['entries'], PagedList)
649 entries = ie_result['entries'].getslice(
650 playliststart, playlistend)
651 n_entries = len(entries)
652 self.to_screen(
653 "[%s] playlist %s: Downloading %d videos" %
654 (ie_result['extractor'], playlist, n_entries))
655
656 for i, entry in enumerate(entries, 1):
657 self.to_screen('[download] Downloading video #%s of %s' % (i, n_entries))
658 extra = {
659 'n_entries': n_entries,
660 'playlist': playlist,
661 'playlist_id': ie_result.get('id'),
662 'playlist_title': ie_result.get('title'),
663 'playlist_index': i + playliststart,
664 'extractor': ie_result['extractor'],
665 'webpage_url': ie_result['webpage_url'],
666 'webpage_url_basename': url_basename(ie_result['webpage_url']),
667 'extractor_key': ie_result['extractor_key'],
668 }
669
670 reason = self._match_entry(entry)
671 if reason is not None:
672 self.to_screen('[download] ' + reason)
673 continue
674
675 entry_result = self.process_ie_result(entry,
676 download=download,
677 extra_info=extra)
678 playlist_results.append(entry_result)
679 ie_result['entries'] = playlist_results
680 return ie_result
681 elif result_type == 'compat_list':
682 self.report_warning(
683 'Extractor %s returned a compat_list result. '
684 'It needs to be updated.' % ie_result.get('extractor'))
685 def _fixup(r):
686 self.add_extra_info(r,
687 {
688 'extractor': ie_result['extractor'],
689 'webpage_url': ie_result['webpage_url'],
690 'webpage_url_basename': url_basename(ie_result['webpage_url']),
691 'extractor_key': ie_result['extractor_key'],
692 })
693 return r
694 ie_result['entries'] = [
695 self.process_ie_result(_fixup(r), download, extra_info)
696 for r in ie_result['entries']
697 ]
698 return ie_result
699 else:
700 raise Exception('Invalid result type: %s' % result_type)
701
702 def select_format(self, format_spec, available_formats):
703 if format_spec == 'best' or format_spec is None:
704 return available_formats[-1]
705 elif format_spec == 'worst':
706 return available_formats[0]
707 elif format_spec == 'bestaudio':
708 audio_formats = [
709 f for f in available_formats
710 if f.get('vcodec') == 'none']
711 if audio_formats:
712 return audio_formats[-1]
713 elif format_spec == 'worstaudio':
714 audio_formats = [
715 f for f in available_formats
716 if f.get('vcodec') == 'none']
717 if audio_formats:
718 return audio_formats[0]
719 elif format_spec == 'bestvideo':
720 video_formats = [
721 f for f in available_formats
722 if f.get('acodec') == 'none']
723 if video_formats:
724 return video_formats[-1]
725 elif format_spec == 'worstvideo':
726 video_formats = [
727 f for f in available_formats
728 if f.get('acodec') == 'none']
729 if video_formats:
730 return video_formats[0]
731 else:
732 extensions = ['mp4', 'flv', 'webm', '3gp', 'm4a']
733 if format_spec in extensions:
734 filter_f = lambda f: f['ext'] == format_spec
735 else:
736 filter_f = lambda f: f['format_id'] == format_spec
737 matches = list(filter(filter_f, available_formats))
738 if matches:
739 return matches[-1]
740 return None
741
742 def process_video_result(self, info_dict, download=True):
743 assert info_dict.get('_type', 'video') == 'video'
744
745 if 'id' not in info_dict:
746 raise ExtractorError('Missing "id" field in extractor result')
747 if 'title' not in info_dict:
748 raise ExtractorError('Missing "title" field in extractor result')
749
750 if 'playlist' not in info_dict:
751 # It isn't part of a playlist
752 info_dict['playlist'] = None
753 info_dict['playlist_index'] = None
754
755 thumbnails = info_dict.get('thumbnails')
756 if thumbnails:
757 thumbnails.sort(key=lambda t: (
758 t.get('width'), t.get('height'), t.get('url')))
759 for t in thumbnails:
760 if 'width' in t and 'height' in t:
761 t['resolution'] = '%dx%d' % (t['width'], t['height'])
762
763 if thumbnails and 'thumbnail' not in info_dict:
764 info_dict['thumbnail'] = thumbnails[-1]['url']
765
766 if 'display_id' not in info_dict and 'id' in info_dict:
767 info_dict['display_id'] = info_dict['id']
768
769 if info_dict.get('upload_date') is None and info_dict.get('timestamp') is not None:
770 upload_date = datetime.datetime.utcfromtimestamp(
771 info_dict['timestamp'])
772 info_dict['upload_date'] = upload_date.strftime('%Y%m%d')
773
774 # This extractors handle format selection themselves
775 if info_dict['extractor'] in ['Youku']:
776 if download:
777 self.process_info(info_dict)
778 return info_dict
779
780 # We now pick which formats have to be downloaded
781 if info_dict.get('formats') is None:
782 # There's only one format available
783 formats = [info_dict]
784 else:
785 formats = info_dict['formats']
786
787 if not formats:
788 raise ExtractorError('No video formats found!')
789
790 # We check that all the formats have the format and format_id fields
791 for i, format in enumerate(formats):
792 if 'url' not in format:
793 raise ExtractorError('Missing "url" key in result (index %d)' % i)
794
795 if format.get('format_id') is None:
796 format['format_id'] = compat_str(i)
797 if format.get('format') is None:
798 format['format'] = '{id} - {res}{note}'.format(
799 id=format['format_id'],
800 res=self.format_resolution(format),
801 note=' ({0})'.format(format['format_note']) if format.get('format_note') is not None else '',
802 )
803 # Automatically determine file extension if missing
804 if 'ext' not in format:
805 format['ext'] = determine_ext(format['url']).lower()
806
807 format_limit = self.params.get('format_limit', None)
808 if format_limit:
809 formats = list(takewhile_inclusive(
810 lambda f: f['format_id'] != format_limit, formats
811 ))
812
813 # TODO Central sorting goes here
814
815 if formats[0] is not info_dict:
816 # only set the 'formats' fields if the original info_dict list them
817 # otherwise we end up with a circular reference, the first (and unique)
818 # element in the 'formats' field in info_dict is info_dict itself,
819 # wich can't be exported to json
820 info_dict['formats'] = formats
821 if self.params.get('listformats', None):
822 self.list_formats(info_dict)
823 return
824
825 req_format = self.params.get('format')
826 if req_format is None:
827 req_format = 'best'
828 formats_to_download = []
829 # The -1 is for supporting YoutubeIE
830 if req_format in ('-1', 'all'):
831 formats_to_download = formats
832 else:
833 for rfstr in req_format.split(','):
834 # We can accept formats requested in the format: 34/5/best, we pick
835 # the first that is available, starting from left
836 req_formats = rfstr.split('/')
837 for rf in req_formats:
838 if re.match(r'.+?\+.+?', rf) is not None:
839 # Two formats have been requested like '137+139'
840 format_1, format_2 = rf.split('+')
841 formats_info = (self.select_format(format_1, formats),
842 self.select_format(format_2, formats))
843 if all(formats_info):
844 # The first format must contain the video and the
845 # second the audio
846 if formats_info[0].get('vcodec') == 'none':
847 self.report_error('The first format must '
848 'contain the video, try using '
849 '"-f %s+%s"' % (format_2, format_1))
850 return
851 selected_format = {
852 'requested_formats': formats_info,
853 'format': rf,
854 'ext': formats_info[0]['ext'],
855 }
856 else:
857 selected_format = None
858 else:
859 selected_format = self.select_format(rf, formats)
860 if selected_format is not None:
861 formats_to_download.append(selected_format)
862 break
863 if not formats_to_download:
864 raise ExtractorError('requested format not available',
865 expected=True)
866
867 if download:
868 if len(formats_to_download) > 1:
869 self.to_screen('[info] %s: downloading video in %s formats' % (info_dict['id'], len(formats_to_download)))
870 for format in formats_to_download:
871 new_info = dict(info_dict)
872 new_info.update(format)
873 self.process_info(new_info)
874 # We update the info dict with the best quality format (backwards compatibility)
875 info_dict.update(formats_to_download[-1])
876 return info_dict
877
878 def process_info(self, info_dict):
879 """Process a single resolved IE result."""
880
881 assert info_dict.get('_type', 'video') == 'video'
882
883 max_downloads = self.params.get('max_downloads')
884 if max_downloads is not None:
885 if self._num_downloads >= int(max_downloads):
886 raise MaxDownloadsReached()
887
888 info_dict['fulltitle'] = info_dict['title']
889 if len(info_dict['title']) > 200:
890 info_dict['title'] = info_dict['title'][:197] + '...'
891
892 # Keep for backwards compatibility
893 info_dict['stitle'] = info_dict['title']
894
895 if 'format' not in info_dict:
896 info_dict['format'] = info_dict['ext']
897
898 reason = self._match_entry(info_dict)
899 if reason is not None:
900 self.to_screen('[download] ' + reason)
901 return
902
903 self._num_downloads += 1
904
905 filename = self.prepare_filename(info_dict)
906
907 # Forced printings
908 if self.params.get('forcetitle', False):
909 self.to_stdout(info_dict['fulltitle'])
910 if self.params.get('forceid', False):
911 self.to_stdout(info_dict['id'])
912 if self.params.get('forceurl', False):
913 # For RTMP URLs, also include the playpath
914 self.to_stdout(info_dict['url'] + info_dict.get('play_path', ''))
915 if self.params.get('forcethumbnail', False) and info_dict.get('thumbnail') is not None:
916 self.to_stdout(info_dict['thumbnail'])
917 if self.params.get('forcedescription', False) and info_dict.get('description') is not None:
918 self.to_stdout(info_dict['description'])
919 if self.params.get('forcefilename', False) and filename is not None:
920 self.to_stdout(filename)
921 if self.params.get('forceduration', False) and info_dict.get('duration') is not None:
922 self.to_stdout(formatSeconds(info_dict['duration']))
923 if self.params.get('forceformat', False):
924 self.to_stdout(info_dict['format'])
925 if self.params.get('forcejson', False):
926 info_dict['_filename'] = filename
927 self.to_stdout(json.dumps(info_dict))
928 if self.params.get('dump_single_json', False):
929 info_dict['_filename'] = filename
930
931 # Do nothing else if in simulate mode
932 if self.params.get('simulate', False):
933 return
934
935 if filename is None:
936 return
937
938 try:
939 dn = os.path.dirname(encodeFilename(filename))
940 if dn and not os.path.exists(dn):
941 os.makedirs(dn)
942 except (OSError, IOError) as err:
943 self.report_error('unable to create directory ' + compat_str(err))
944 return
945
946 if self.params.get('writedescription', False):
947 descfn = filename + '.description'
948 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(descfn)):
949 self.to_screen('[info] Video description is already present')
950 else:
951 try:
952 self.to_screen('[info] Writing video description to: ' + descfn)
953 with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
954 descfile.write(info_dict['description'])
955 except (KeyError, TypeError):
956 self.report_warning('There\'s no description to write.')
957 except (OSError, IOError):
958 self.report_error('Cannot write description file ' + descfn)
959 return
960
961 if self.params.get('writeannotations', False):
962 annofn = filename + '.annotations.xml'
963 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(annofn)):
964 self.to_screen('[info] Video annotations are already present')
965 else:
966 try:
967 self.to_screen('[info] Writing video annotations to: ' + annofn)
968 with io.open(encodeFilename(annofn), 'w', encoding='utf-8') as annofile:
969 annofile.write(info_dict['annotations'])
970 except (KeyError, TypeError):
971 self.report_warning('There are no annotations to write.')
972 except (OSError, IOError):
973 self.report_error('Cannot write annotations file: ' + annofn)
974 return
975
976 subtitles_are_requested = any([self.params.get('writesubtitles', False),
977 self.params.get('writeautomaticsub')])
978
979 if subtitles_are_requested and 'subtitles' in info_dict and info_dict['subtitles']:
980 # subtitles download errors are already managed as troubles in relevant IE
981 # that way it will silently go on when used with unsupporting IE
982 subtitles = info_dict['subtitles']
983 sub_format = self.params.get('subtitlesformat', 'srt')
984 for sub_lang in subtitles.keys():
985 sub = subtitles[sub_lang]
986 if sub is None:
987 continue
988 try:
989 sub_filename = subtitles_filename(filename, sub_lang, sub_format)
990 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(sub_filename)):
991 self.to_screen('[info] Video subtitle %s.%s is already_present' % (sub_lang, sub_format))
992 else:
993 self.to_screen('[info] Writing video subtitles to: ' + sub_filename)
994 with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
995 subfile.write(sub)
996 except (OSError, IOError):
997 self.report_error('Cannot write subtitles file ' + sub_filename)
998 return
999
1000 if self.params.get('writeinfojson', False):
1001 infofn = os.path.splitext(filename)[0] + '.info.json'
1002 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(infofn)):
1003 self.to_screen('[info] Video description metadata is already present')
1004 else:
1005 self.to_screen('[info] Writing video description metadata as JSON to: ' + infofn)
1006 try:
1007 write_json_file(info_dict, infofn)
1008 except (OSError, IOError):
1009 self.report_error('Cannot write metadata to JSON file ' + infofn)
1010 return
1011
1012 if self.params.get('writethumbnail', False):
1013 if info_dict.get('thumbnail') is not None:
1014 thumb_format = determine_ext(info_dict['thumbnail'], 'jpg')
1015 thumb_filename = os.path.splitext(filename)[0] + '.' + thumb_format
1016 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(thumb_filename)):
1017 self.to_screen('[%s] %s: Thumbnail is already present' %
1018 (info_dict['extractor'], info_dict['id']))
1019 else:
1020 self.to_screen('[%s] %s: Downloading thumbnail ...' %
1021 (info_dict['extractor'], info_dict['id']))
1022 try:
1023 uf = self.urlopen(info_dict['thumbnail'])
1024 with open(thumb_filename, 'wb') as thumbf:
1025 shutil.copyfileobj(uf, thumbf)
1026 self.to_screen('[%s] %s: Writing thumbnail to: %s' %
1027 (info_dict['extractor'], info_dict['id'], thumb_filename))
1028 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
1029 self.report_warning('Unable to download thumbnail "%s": %s' %
1030 (info_dict['thumbnail'], compat_str(err)))
1031
1032 if not self.params.get('skip_download', False):
1033 if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
1034 success = True
1035 else:
1036 try:
1037 def dl(name, info):
1038 fd = get_suitable_downloader(info)(self, self.params)
1039 for ph in self._progress_hooks:
1040 fd.add_progress_hook(ph)
1041 if self.params.get('verbose'):
1042 self.to_stdout('[debug] Invoking downloader on %r' % info.get('url'))
1043 return fd.download(name, info)
1044 if info_dict.get('requested_formats') is not None:
1045 downloaded = []
1046 success = True
1047 merger = FFmpegMergerPP(self, not self.params.get('keepvideo'))
1048 if not merger._executable:
1049 postprocessors = []
1050 self.report_warning('You have requested multiple '
1051 'formats but ffmpeg or avconv are not installed.'
1052 ' The formats won\'t be merged')
1053 else:
1054 postprocessors = [merger]
1055 for f in info_dict['requested_formats']:
1056 new_info = dict(info_dict)
1057 new_info.update(f)
1058 fname = self.prepare_filename(new_info)
1059 fname = prepend_extension(fname, 'f%s' % f['format_id'])
1060 downloaded.append(fname)
1061 partial_success = dl(fname, new_info)
1062 success = success and partial_success
1063 info_dict['__postprocessors'] = postprocessors
1064 info_dict['__files_to_merge'] = downloaded
1065 else:
1066 # Just a single file
1067 success = dl(filename, info_dict)
1068 except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
1069 self.report_error('unable to download video data: %s' % str(err))
1070 return
1071 except (OSError, IOError) as err:
1072 raise UnavailableVideoError(err)
1073 except (ContentTooShortError, ) as err:
1074 self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
1075 return
1076
1077 if success:
1078 try:
1079 self.post_process(filename, info_dict)
1080 except (PostProcessingError) as err:
1081 self.report_error('postprocessing: %s' % str(err))
1082 return
1083
1084 self.record_download_archive(info_dict)
1085
1086 def download(self, url_list):
1087 """Download a given list of URLs."""
1088 outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
1089 if (len(url_list) > 1 and
1090 '%' not in outtmpl
1091 and self.params.get('max_downloads') != 1):
1092 raise SameFileError(outtmpl)
1093
1094 for url in url_list:
1095 try:
1096 #It also downloads the videos
1097 res = self.extract_info(url)
1098 except UnavailableVideoError:
1099 self.report_error('unable to download video')
1100 except MaxDownloadsReached:
1101 self.to_screen('[info] Maximum number of downloaded files reached.')
1102 raise
1103 else:
1104 if self.params.get('dump_single_json', False):
1105 self.to_stdout(json.dumps(res))
1106
1107 return self._download_retcode
1108
1109 def download_with_info_file(self, info_filename):
1110 with io.open(info_filename, 'r', encoding='utf-8') as f:
1111 info = json.load(f)
1112 try:
1113 self.process_ie_result(info, download=True)
1114 except DownloadError:
1115 webpage_url = info.get('webpage_url')
1116 if webpage_url is not None:
1117 self.report_warning('The info failed to download, trying with "%s"' % webpage_url)
1118 return self.download([webpage_url])
1119 else:
1120 raise
1121 return self._download_retcode
1122
1123 def post_process(self, filename, ie_info):
1124 """Run all the postprocessors on the given file."""
1125 info = dict(ie_info)
1126 info['filepath'] = filename
1127 keep_video = None
1128 pps_chain = []
1129 if ie_info.get('__postprocessors') is not None:
1130 pps_chain.extend(ie_info['__postprocessors'])
1131 pps_chain.extend(self._pps)
1132 for pp in pps_chain:
1133 try:
1134 keep_video_wish, new_info = pp.run(info)
1135 if keep_video_wish is not None:
1136 if keep_video_wish:
1137 keep_video = keep_video_wish
1138 elif keep_video is None:
1139 # No clear decision yet, let IE decide
1140 keep_video = keep_video_wish
1141 except PostProcessingError as e:
1142 self.report_error(e.msg)
1143 if keep_video is False and not self.params.get('keepvideo', False):
1144 try:
1145 self.to_screen('Deleting original file %s (pass -k to keep)' % filename)
1146 os.remove(encodeFilename(filename))
1147 except (IOError, OSError):
1148 self.report_warning('Unable to remove downloaded video file')
1149
1150 def _make_archive_id(self, info_dict):
1151 # Future-proof against any change in case
1152 # and backwards compatibility with prior versions
1153 extractor = info_dict.get('extractor_key')
1154 if extractor is None:
1155 if 'id' in info_dict:
1156 extractor = info_dict.get('ie_key') # key in a playlist
1157 if extractor is None:
1158 return None # Incomplete video information
1159 return extractor.lower() + ' ' + info_dict['id']
1160
1161 def in_download_archive(self, info_dict):
1162 fn = self.params.get('download_archive')
1163 if fn is None:
1164 return False
1165
1166 vid_id = self._make_archive_id(info_dict)
1167 if vid_id is None:
1168 return False # Incomplete video information
1169
1170 try:
1171 with locked_file(fn, 'r', encoding='utf-8') as archive_file:
1172 for line in archive_file:
1173 if line.strip() == vid_id:
1174 return True
1175 except IOError as ioe:
1176 if ioe.errno != errno.ENOENT:
1177 raise
1178 return False
1179
1180 def record_download_archive(self, info_dict):
1181 fn = self.params.get('download_archive')
1182 if fn is None:
1183 return
1184 vid_id = self._make_archive_id(info_dict)
1185 assert vid_id
1186 with locked_file(fn, 'a', encoding='utf-8') as archive_file:
1187 archive_file.write(vid_id + '\n')
1188
1189 @staticmethod
1190 def format_resolution(format, default='unknown'):
1191 if format.get('vcodec') == 'none':
1192 return 'audio only'
1193 if format.get('resolution') is not None:
1194 return format['resolution']
1195 if format.get('height') is not None:
1196 if format.get('width') is not None:
1197 res = '%sx%s' % (format['width'], format['height'])
1198 else:
1199 res = '%sp' % format['height']
1200 elif format.get('width') is not None:
1201 res = '?x%d' % format['width']
1202 else:
1203 res = default
1204 return res
1205
1206 def _format_note(self, fdict):
1207 res = ''
1208 if fdict.get('ext') in ['f4f', 'f4m']:
1209 res += '(unsupported) '
1210 if fdict.get('format_note') is not None:
1211 res += fdict['format_note'] + ' '
1212 if fdict.get('tbr') is not None:
1213 res += '%4dk ' % fdict['tbr']
1214 if fdict.get('container') is not None:
1215 if res:
1216 res += ', '
1217 res += '%s container' % fdict['container']
1218 if (fdict.get('vcodec') is not None and
1219 fdict.get('vcodec') != 'none'):
1220 if res:
1221 res += ', '
1222 res += fdict['vcodec']
1223 if fdict.get('vbr') is not None:
1224 res += '@'
1225 elif fdict.get('vbr') is not None and fdict.get('abr') is not None:
1226 res += 'video@'
1227 if fdict.get('vbr') is not None:
1228 res += '%4dk' % fdict['vbr']
1229 if fdict.get('fps') is not None:
1230 res += ', %sfps' % fdict['fps']
1231 if fdict.get('acodec') is not None:
1232 if res:
1233 res += ', '
1234 if fdict['acodec'] == 'none':
1235 res += 'video only'
1236 else:
1237 res += '%-5s' % fdict['acodec']
1238 elif fdict.get('abr') is not None:
1239 if res:
1240 res += ', '
1241 res += 'audio'
1242 if fdict.get('abr') is not None:
1243 res += '@%3dk' % fdict['abr']
1244 if fdict.get('asr') is not None:
1245 res += ' (%5dHz)' % fdict['asr']
1246 if fdict.get('filesize') is not None:
1247 if res:
1248 res += ', '
1249 res += format_bytes(fdict['filesize'])
1250 elif fdict.get('filesize_approx') is not None:
1251 if res:
1252 res += ', '
1253 res += '~' + format_bytes(fdict['filesize_approx'])
1254 return res
1255
1256 def list_formats(self, info_dict):
1257 def line(format, idlen=20):
1258 return (('%-' + compat_str(idlen + 1) + 's%-10s%-12s%s') % (
1259 format['format_id'],
1260 format['ext'],
1261 self.format_resolution(format),
1262 self._format_note(format),
1263 ))
1264
1265 formats = info_dict.get('formats', [info_dict])
1266 idlen = max(len('format code'),
1267 max(len(f['format_id']) for f in formats))
1268 formats_s = [line(f, idlen) for f in formats]
1269 if len(formats) > 1:
1270 formats_s[0] += (' ' if self._format_note(formats[0]) else '') + '(worst)'
1271 formats_s[-1] += (' ' if self._format_note(formats[-1]) else '') + '(best)'
1272
1273 header_line = line({
1274 'format_id': 'format code', 'ext': 'extension',
1275 'resolution': 'resolution', 'format_note': 'note'}, idlen=idlen)
1276 self.to_screen('[info] Available formats for %s:\n%s\n%s' %
1277 (info_dict['id'], header_line, '\n'.join(formats_s)))
1278
1279 def urlopen(self, req):
1280 """ Start an HTTP download """
1281
1282 # According to RFC 3986, URLs can not contain non-ASCII characters, however this is not
1283 # always respected by websites, some tend to give out URLs with non percent-encoded
1284 # non-ASCII characters (see telemb.py, ard.py [#3412])
1285 # urllib chokes on URLs with non-ASCII characters (see http://bugs.python.org/issue3991)
1286 # To work around aforementioned issue we will replace request's original URL with
1287 # percent-encoded one
1288 req_is_string = isinstance(req, basestring if sys.version_info < (3, 0) else compat_str)
1289 url = req if req_is_string else req.get_full_url()
1290 url_escaped = escape_url(url)
1291
1292 # Substitute URL if any change after escaping
1293 if url != url_escaped:
1294 if req_is_string:
1295 req = url_escaped
1296 else:
1297 req = compat_urllib_request.Request(
1298 url_escaped, data=req.data, headers=req.headers,
1299 origin_req_host=req.origin_req_host, unverifiable=req.unverifiable)
1300
1301 return self._opener.open(req, timeout=self._socket_timeout)
1302
1303 def print_debug_header(self):
1304 if not self.params.get('verbose'):
1305 return
1306
1307 if type('') is not compat_str:
1308 # Python 2.6 on SLES11 SP1 (https://github.com/rg3/youtube-dl/issues/3326)
1309 self.report_warning(
1310 'Your Python is broken! Update to a newer and supported version')
1311
1312 stdout_encoding = getattr(
1313 sys.stdout, 'encoding', 'missing (%s)' % type(sys.stdout).__name__)
1314 encoding_str = (
1315 '[debug] Encodings: locale %s, fs %s, out %s, pref %s\n' % (
1316 locale.getpreferredencoding(),
1317 sys.getfilesystemencoding(),
1318 stdout_encoding,
1319 self.get_encoding()))
1320 write_string(encoding_str, encoding=None)
1321
1322 self._write_string('[debug] youtube-dl version ' + __version__ + '\n')
1323 try:
1324 sp = subprocess.Popen(
1325 ['git', 'rev-parse', '--short', 'HEAD'],
1326 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1327 cwd=os.path.dirname(os.path.abspath(__file__)))
1328 out, err = sp.communicate()
1329 out = out.decode().strip()
1330 if re.match('[0-9a-f]+', out):
1331 self._write_string('[debug] Git HEAD: ' + out + '\n')
1332 except:
1333 try:
1334 sys.exc_clear()
1335 except:
1336 pass
1337 self._write_string('[debug] Python version %s - %s\n' % (
1338 platform.python_version(), platform_name()))
1339
1340 exe_versions = FFmpegPostProcessor.get_versions()
1341 exe_versions['rtmpdump'] = rtmpdump_version()
1342 exe_str = ', '.join(
1343 '%s %s' % (exe, v)
1344 for exe, v in sorted(exe_versions.items())
1345 if v
1346 )
1347 if not exe_str:
1348 exe_str = 'none'
1349 self._write_string('[debug] exe versions: %s\n' % exe_str)
1350
1351 proxy_map = {}
1352 for handler in self._opener.handlers:
1353 if hasattr(handler, 'proxies'):
1354 proxy_map.update(handler.proxies)
1355 self._write_string('[debug] Proxy map: ' + compat_str(proxy_map) + '\n')
1356
1357 def _setup_opener(self):
1358 timeout_val = self.params.get('socket_timeout')
1359 self._socket_timeout = 600 if timeout_val is None else float(timeout_val)
1360
1361 opts_cookiefile = self.params.get('cookiefile')
1362 opts_proxy = self.params.get('proxy')
1363
1364 if opts_cookiefile is None:
1365 self.cookiejar = compat_cookiejar.CookieJar()
1366 else:
1367 self.cookiejar = compat_cookiejar.MozillaCookieJar(
1368 opts_cookiefile)
1369 if os.access(opts_cookiefile, os.R_OK):
1370 self.cookiejar.load()
1371
1372 cookie_processor = compat_urllib_request.HTTPCookieProcessor(
1373 self.cookiejar)
1374 if opts_proxy is not None:
1375 if opts_proxy == '':
1376 proxies = {}
1377 else:
1378 proxies = {'http': opts_proxy, 'https': opts_proxy}
1379 else:
1380 proxies = compat_urllib_request.getproxies()
1381 # Set HTTPS proxy to HTTP one if given (https://github.com/rg3/youtube-dl/issues/805)
1382 if 'http' in proxies and 'https' not in proxies:
1383 proxies['https'] = proxies['http']
1384 proxy_handler = compat_urllib_request.ProxyHandler(proxies)
1385
1386 debuglevel = 1 if self.params.get('debug_printtraffic') else 0
1387 https_handler = make_HTTPS_handler(
1388 self.params.get('nocheckcertificate', False), debuglevel=debuglevel)
1389 ydlh = YoutubeDLHandler(debuglevel=debuglevel)
1390 opener = compat_urllib_request.build_opener(
1391 https_handler, proxy_handler, cookie_processor, ydlh)
1392 # Delete the default user-agent header, which would otherwise apply in
1393 # cases where our custom HTTP handler doesn't come into play
1394 # (See https://github.com/rg3/youtube-dl/issues/1309 for details)
1395 opener.addheaders = []
1396 self._opener = opener
1397
1398 def encode(self, s):
1399 if isinstance(s, bytes):
1400 return s # Already encoded
1401
1402 try:
1403 return s.encode(self.get_encoding())
1404 except UnicodeEncodeError as err:
1405 err.reason = err.reason + '. Check your system encoding configuration or use the --encoding option.'
1406 raise
1407
1408 def get_encoding(self):
1409 encoding = self.params.get('encoding')
1410 if encoding is None:
1411 encoding = preferredencoding()
1412 return encoding