| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  | from __future__ import unicode_literals | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  | import os | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from .common import FileDownloader | 
					
						
							| 
									
										
										
										
											2014-12-13 12:24:42 +01:00
										 |  |  | from ..compat import ( | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |     compat_urllib_request, | 
					
						
							|  |  |  |     compat_urllib_error, | 
					
						
							| 
									
										
										
										
											2014-12-13 12:24:42 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | from ..utils import ( | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |     ContentTooShortError, | 
					
						
							|  |  |  |     encodeFilename, | 
					
						
							|  |  |  |     sanitize_open, | 
					
						
							|  |  |  |     format_bytes, | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class HttpFD(FileDownloader): | 
					
						
							|  |  |  |     def real_download(self, filename, info_dict): | 
					
						
							|  |  |  |         url = info_dict['url'] | 
					
						
							|  |  |  |         tmpfilename = self.temp_name(filename) | 
					
						
							|  |  |  |         stream = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Do not include the Accept-Encoding header | 
					
						
							|  |  |  |         headers = {'Youtubedl-no-compression': 'True'} | 
					
						
							|  |  |  |         if 'user_agent' in info_dict: | 
					
						
							|  |  |  |             headers['Youtubedl-user-agent'] = info_dict['user_agent'] | 
					
						
							| 
									
										
										
										
											2014-03-25 21:53:26 +07:00
										 |  |  |         if 'http_referer' in info_dict: | 
					
						
							|  |  |  |             headers['Referer'] = info_dict['http_referer'] | 
					
						
							| 
									
										
										
										
											2014-08-24 01:31:35 +02:00
										 |  |  |         add_headers = info_dict.get('http_headers') | 
					
						
							|  |  |  |         if add_headers: | 
					
						
							|  |  |  |             headers.update(add_headers) | 
					
						
							|  |  |  |         data = info_dict.get('http_post_data') | 
					
						
							|  |  |  |         http_method = info_dict.get('http_method') | 
					
						
							|  |  |  |         basic_request = compat_urllib_request.Request(url, data, headers) | 
					
						
							|  |  |  |         request = compat_urllib_request.Request(url, data, headers) | 
					
						
							|  |  |  |         if http_method is not None: | 
					
						
							|  |  |  |             basic_request.get_method = lambda: http_method | 
					
						
							|  |  |  |             request.get_method = lambda: http_method | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-27 09:32:01 +07:00
										 |  |  |         is_test = self.params.get('test', False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if is_test: | 
					
						
							|  |  |  |             request.add_header('Range', 'bytes=0-%s' % str(self._TEST_FILE_SIZE - 1)) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Establish possible resume length | 
					
						
							|  |  |  |         if os.path.isfile(encodeFilename(tmpfilename)): | 
					
						
							|  |  |  |             resume_len = os.path.getsize(encodeFilename(tmpfilename)) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             resume_len = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         open_mode = 'wb' | 
					
						
							|  |  |  |         if resume_len != 0: | 
					
						
							|  |  |  |             if self.params.get('continuedl', False): | 
					
						
							|  |  |  |                 self.report_resuming_byte(resume_len) | 
					
						
							| 
									
										
										
										
											2014-01-25 12:02:43 +01:00
										 |  |  |                 request.add_header('Range', 'bytes=%d-' % resume_len) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                 open_mode = 'ab' | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 resume_len = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         count = 0 | 
					
						
							|  |  |  |         retries = self.params.get('retries', 0) | 
					
						
							|  |  |  |         while count <= retries: | 
					
						
							|  |  |  |             # Establish connection | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2014-03-07 16:41:42 +01:00
										 |  |  |                 data = self.ydl.urlopen(request) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                 break | 
					
						
							|  |  |  |             except (compat_urllib_error.HTTPError, ) as err: | 
					
						
							|  |  |  |                 if (err.code < 500 or err.code >= 600) and err.code != 416: | 
					
						
							|  |  |  |                     # Unexpected HTTP error | 
					
						
							|  |  |  |                     raise | 
					
						
							|  |  |  |                 elif err.code == 416: | 
					
						
							|  |  |  |                     # Unable to resume (requested range not satisfiable) | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         # Open the connection again without the range header | 
					
						
							| 
									
										
										
										
											2014-03-07 16:41:42 +01:00
										 |  |  |                         data = self.ydl.urlopen(basic_request) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                         content_length = data.info()['Content-Length'] | 
					
						
							|  |  |  |                     except (compat_urllib_error.HTTPError, ) as err: | 
					
						
							|  |  |  |                         if err.code < 500 or err.code >= 600: | 
					
						
							|  |  |  |                             raise | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         # Examine the reported length | 
					
						
							|  |  |  |                         if (content_length is not None and | 
					
						
							|  |  |  |                                 (resume_len - 100 < int(content_length) < resume_len + 100)): | 
					
						
							|  |  |  |                             # The file had already been fully downloaded. | 
					
						
							|  |  |  |                             # Explanation to the above condition: in issue #175 it was revealed that | 
					
						
							|  |  |  |                             # YouTube sometimes adds or removes a few bytes from the end of the file, | 
					
						
							|  |  |  |                             # changing the file size slightly and causing problems for some users. So | 
					
						
							|  |  |  |                             # I decided to implement a suggested change and consider the file | 
					
						
							|  |  |  |                             # completely downloaded if the file size differs less than 100 bytes from | 
					
						
							|  |  |  |                             # the one in the hard drive. | 
					
						
							|  |  |  |                             self.report_file_already_downloaded(filename) | 
					
						
							|  |  |  |                             self.try_rename(tmpfilename, filename) | 
					
						
							|  |  |  |                             self._hook_progress({ | 
					
						
							|  |  |  |                                 'filename': filename, | 
					
						
							|  |  |  |                                 'status': 'finished', | 
					
						
							|  |  |  |                             }) | 
					
						
							|  |  |  |                             return True | 
					
						
							|  |  |  |                         else: | 
					
						
							|  |  |  |                             # The length does not match, we start the download over | 
					
						
							|  |  |  |                             self.report_unable_to_resume() | 
					
						
							| 
									
										
										
										
											2014-02-26 02:47:27 +01:00
										 |  |  |                             resume_len = 0 | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                             open_mode = 'wb' | 
					
						
							|  |  |  |                             break | 
					
						
							|  |  |  |             # Retry | 
					
						
							|  |  |  |             count += 1 | 
					
						
							|  |  |  |             if count <= retries: | 
					
						
							|  |  |  |                 self.report_retry(count, retries) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if count > retries: | 
					
						
							| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  |             self.report_error('giving up after %s retries' % retries) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         data_len = data.info().get('Content-length', None) | 
					
						
							| 
									
										
										
										
											2014-04-27 09:32:01 +07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Range HTTP header may be ignored/unsupported by a webserver | 
					
						
							|  |  |  |         # (e.g. extractor/scivee.py, extractor/bambuser.py). | 
					
						
							|  |  |  |         # However, for a test we still would like to download just a piece of a file. | 
					
						
							|  |  |  |         # To achieve this we limit data_len to _TEST_FILE_SIZE and manually control | 
					
						
							|  |  |  |         # block size when downloading a file. | 
					
						
							| 
									
										
										
										
											2014-04-30 20:02:17 +07:00
										 |  |  |         if is_test and (data_len is None or int(data_len) > self._TEST_FILE_SIZE): | 
					
						
							| 
									
										
										
										
											2014-04-27 09:32:01 +07:00
										 |  |  |             data_len = self._TEST_FILE_SIZE | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |         if data_len is not None: | 
					
						
							|  |  |  |             data_len = int(data_len) + resume_len | 
					
						
							|  |  |  |             min_data_len = self.params.get("min_filesize", None) | 
					
						
							| 
									
										
										
										
											2014-01-25 12:02:43 +01:00
										 |  |  |             max_data_len = self.params.get("max_filesize", None) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             if min_data_len is not None and data_len < min_data_len: | 
					
						
							| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  |                 self.to_screen('\r[download] File is smaller than min-filesize (%s bytes < %s bytes). Aborting.' % (data_len, min_data_len)) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                 return False | 
					
						
							|  |  |  |             if max_data_len is not None and data_len > max_data_len: | 
					
						
							| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  |                 self.to_screen('\r[download] File is larger than max-filesize (%s bytes > %s bytes). Aborting.' % (data_len, max_data_len)) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                 return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         data_len_str = format_bytes(data_len) | 
					
						
							|  |  |  |         byte_counter = 0 + resume_len | 
					
						
							|  |  |  |         block_size = self.params.get('buffersize', 1024) | 
					
						
							|  |  |  |         start = time.time() | 
					
						
							| 
									
										
										
										
											2014-07-31 03:08:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # measure time over whole while-loop, so slow_down() and best_block_size() work together properly | 
					
						
							|  |  |  |         now = None  # needed for slow_down() in the first loop run | 
					
						
							|  |  |  |         before = start  # start measuring | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |         while True: | 
					
						
							| 
									
										
										
										
											2014-07-31 03:08:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             # Download and write | 
					
						
							| 
									
										
										
										
											2014-04-27 09:32:01 +07:00
										 |  |  |             data_block = data.read(block_size if not is_test else min(block_size, data_len - byte_counter)) | 
					
						
							| 
									
										
										
										
											2014-07-31 03:08:24 +02:00
										 |  |  |             byte_counter += len(data_block) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # exit loop when download is finished | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             if len(data_block) == 0: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-31 03:08:24 +02:00
										 |  |  |             # Open destination file just in time | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             if stream is None: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     (stream, tmpfilename) = sanitize_open(tmpfilename, open_mode) | 
					
						
							|  |  |  |                     assert stream is not None | 
					
						
							|  |  |  |                     filename = self.undo_temp_name(tmpfilename) | 
					
						
							|  |  |  |                     self.report_destination(filename) | 
					
						
							|  |  |  |                 except (OSError, IOError) as err: | 
					
						
							| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  |                     self.report_error('unable to open for writing: %s' % str(err)) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                     return False | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 stream.write(data_block) | 
					
						
							| 
									
										
										
										
											2013-12-31 13:44:57 +01:00
										 |  |  |             except (IOError, OSError) as err: | 
					
						
							| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  |                 self.to_stderr('\n') | 
					
						
							|  |  |  |                 self.report_error('unable to write data: %s' % str(err)) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |                 return False | 
					
						
							| 
									
										
										
										
											2014-07-31 03:08:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Apply rate limit | 
					
						
							|  |  |  |             self.slow_down(start, now, byte_counter - resume_len) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # end measuring of one loop run | 
					
						
							|  |  |  |             now = time.time() | 
					
						
							|  |  |  |             after = now | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Adjust block size | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             if not self.params.get('noresizebuffer', False): | 
					
						
							|  |  |  |                 block_size = self.best_block_size(after - before, len(data_block)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-07-31 03:08:24 +02:00
										 |  |  |             before = after | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             # Progress message | 
					
						
							| 
									
										
										
										
											2014-07-31 03:08:24 +02:00
										 |  |  |             speed = self.calc_speed(start, now, byte_counter - resume_len) | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             if data_len is None: | 
					
						
							|  |  |  |                 eta = percent = None | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 percent = self.calc_percent(byte_counter, data_len) | 
					
						
							|  |  |  |                 eta = self.calc_eta(start, time.time(), data_len - resume_len, byte_counter - resume_len) | 
					
						
							|  |  |  |             self.report_progress(percent, data_len_str, speed, eta) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self._hook_progress({ | 
					
						
							|  |  |  |                 'downloaded_bytes': byte_counter, | 
					
						
							|  |  |  |                 'total_bytes': data_len, | 
					
						
							|  |  |  |                 'tmpfilename': tmpfilename, | 
					
						
							|  |  |  |                 'filename': filename, | 
					
						
							|  |  |  |                 'status': 'downloading', | 
					
						
							|  |  |  |                 'eta': eta, | 
					
						
							|  |  |  |                 'speed': speed, | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-27 09:32:01 +07:00
										 |  |  |             if is_test and byte_counter == data_len: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |         if stream is None: | 
					
						
							| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  |             self.to_stderr('\n') | 
					
						
							|  |  |  |             self.report_error('Did not get any data blocks') | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |             return False | 
					
						
							| 
									
										
										
										
											2014-11-26 12:27:36 +01:00
										 |  |  |         if tmpfilename != '-': | 
					
						
							| 
									
										
										
										
											2014-09-05 22:05:36 +07:00
										 |  |  |             stream.close() | 
					
						
							| 
									
										
										
										
											2013-09-23 17:59:27 +02:00
										 |  |  |         self.report_finish(data_len_str, (time.time() - start)) | 
					
						
							|  |  |  |         if data_len is not None and byte_counter != data_len: | 
					
						
							|  |  |  |             raise ContentTooShortError(byte_counter, int(data_len)) | 
					
						
							|  |  |  |         self.try_rename(tmpfilename, filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Update file modification time | 
					
						
							|  |  |  |         if self.params.get('updatetime', True): | 
					
						
							|  |  |  |             info_dict['filetime'] = self.try_utime(filename, data.info().get('last-modified', None)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self._hook_progress({ | 
					
						
							|  |  |  |             'downloaded_bytes': byte_counter, | 
					
						
							|  |  |  |             'total_bytes': byte_counter, | 
					
						
							|  |  |  |             'filename': filename, | 
					
						
							|  |  |  |             'status': 'finished', | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return True |