]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/downloader/common.py
10143d56a201ca9d7b9b919ab38880adea0c1cf6
  13 class FileDownloader(object): 
  14     """File Downloader class. 
  16     File downloader objects are the ones responsible of downloading the 
  17     actual video file and writing it to disk. 
  19     File downloaders accept a lot of parameters. In order not to saturate 
  20     the object constructor with arguments, it receives a dictionary of 
  25     verbose:           Print additional info to stdout. 
  26     quiet:             Do not print messages to stdout. 
  27     ratelimit:         Download speed limit, in bytes/sec. 
  28     retries:           Number of times to retry for HTTP error 5xx 
  29     buffersize:        Size of download buffer in bytes. 
  30     noresizebuffer:    Do not automatically resize the download buffer. 
  31     continuedl:        Try to continue downloads if possible. 
  32     noprogress:        Do not print the progress bar. 
  33     logtostderr:       Log messages to stderr instead of stdout. 
  34     consoletitle:      Display progress in console window's titlebar. 
  35     nopart:            Do not use temporary .part files. 
  36     updatetime:        Use the Last-modified header to set output file timestamps. 
  37     test:              Download only first bytes to test the downloader. 
  38     min_filesize:      Skip files smaller than this size 
  39     max_filesize:      Skip files larger than this size 
  41     Subclasses of this one must re-define the real_download method. 
  46     def __init__(self
, ydl
, params
): 
  47         """Create a FileDownloader object with the given options.""" 
  49         self
._progress
_hooks 
= [] 
  53     def format_seconds(seconds
): 
  54         (mins
, secs
) = divmod(seconds
, 60) 
  55         (hours
, mins
) = divmod(mins
, 60) 
  59             return '%02d:%02d' % (mins
, secs
) 
  61             return '%02d:%02d:%02d' % (hours
, mins
, secs
) 
  64     def calc_percent(byte_counter
, data_len
): 
  67         return float(byte_counter
) / float(data_len
) * 100.0 
  70     def format_percent(percent
): 
  73         return '%6s' % ('%3.1f%%' % percent
) 
  76     def calc_eta(start
, now
, total
, current
): 
  80         if current 
== 0 or dif 
< 0.001: # One millisecond 
  82         rate 
= float(current
) / dif
 
  83         return int((float(total
) - float(current
)) / rate
) 
  89         return FileDownloader
.format_seconds(eta
) 
  92     def calc_speed(start
, now
, bytes): 
  94         if bytes == 0 or dif 
< 0.001: # One millisecond 
  96         return float(bytes) / dif
 
  99     def format_speed(speed
): 
 101             return '%10s' % '---b/s' 
 102         return '%10s' % ('%s/s' % format_bytes(speed
)) 
 105     def best_block_size(elapsed_time
, bytes): 
 106         new_min 
= max(bytes / 2.0, 1.0) 
 107         new_max 
= min(max(bytes * 2.0, 1.0), 4194304) # Do not surpass 4 MB 
 108         if elapsed_time 
< 0.001: 
 110         rate 
= bytes / elapsed_time
 
 118     def parse_bytes(bytestr
): 
 119         """Parse a string indicating a byte quantity into an integer.""" 
 120         matchobj 
= re
.match(r
'(?i)^(\d+(?:\.\d+)?)([kMGTPEZY]?)$', bytestr
) 
 123         number 
= float(matchobj
.group(1)) 
 124         multiplier 
= 1024.0 ** 'bkmgtpezy'.index(matchobj
.group(2).lower()) 
 125         return int(round(number 
* multiplier
)) 
 127     def to_screen(self
, *args
, **kargs
): 
 128         self
.ydl
.to_screen(*args
, **kargs
) 
 130     def to_stderr(self
, message
): 
 131         self
.ydl
.to_screen(message
) 
 133     def to_console_title(self
, message
): 
 134         self
.ydl
.to_console_title(message
) 
 136     def trouble(self
, *args
, **kargs
): 
 137         self
.ydl
.trouble(*args
, **kargs
) 
 139     def report_warning(self
, *args
, **kargs
): 
 140         self
.ydl
.report_warning(*args
, **kargs
) 
 142     def report_error(self
, *args
, **kargs
): 
 143         self
.ydl
.report_error(*args
, **kargs
) 
 145     def slow_down(self
, start_time
, byte_counter
): 
 146         """Sleep if the download speed is over the rate limit.""" 
 147         rate_limit 
= self
.params
.get('ratelimit', None) 
 148         if rate_limit 
is None or byte_counter 
== 0: 
 151         elapsed 
= now 
- start_time
 
 154         speed 
= float(byte_counter
) / elapsed
 
 155         if speed 
> rate_limit
: 
 156             time
.sleep((byte_counter 
- rate_limit 
* (now 
- start_time
)) / rate_limit
) 
 158     def temp_name(self
, filename
): 
 159         """Returns a temporary filename for the given filename.""" 
 160         if self
.params
.get('nopart', False) or filename 
== u
'-' or \
 
 161                 (os
.path
.exists(encodeFilename(filename
)) and not os
.path
.isfile(encodeFilename(filename
))): 
 163         return filename 
+ u
'.part' 
 165     def undo_temp_name(self
, filename
): 
 166         if filename
.endswith(u
'.part'): 
 167             return filename
[:-len(u
'.part')] 
 170     def try_rename(self
, old_filename
, new_filename
): 
 172             if old_filename 
== new_filename
: 
 174             os
.rename(encodeFilename(old_filename
), encodeFilename(new_filename
)) 
 175         except (IOError, OSError) as err
: 
 176             self
.report_error(u
'unable to rename file: %s' % str(err
)) 
 178     def try_utime(self
, filename
, last_modified_hdr
): 
 179         """Try to set the last-modified time of the given file.""" 
 180         if last_modified_hdr 
is None: 
 182         if not os
.path
.isfile(encodeFilename(filename
)): 
 184         timestr 
= last_modified_hdr
 
 187         filetime 
= timeconvert(timestr
) 
 190         # Ignore obviously invalid dates 
 194             os
.utime(filename
, (time
.time(), filetime
)) 
 199     def report_destination(self
, filename
): 
 200         """Report destination filename.""" 
 201         self
.to_screen(u
'[download] Destination: ' + filename
) 
 203     def _report_progress_status(self
, msg
, is_last_line
=False): 
 204         fullmsg 
= u
'[download] ' + msg
 
 205         if self
.params
.get('progress_with_newline', False): 
 206             self
.to_screen(fullmsg
) 
 209                 prev_len 
= getattr(self
, '_report_progress_prev_line_length', 
 211                 if prev_len 
> len(fullmsg
): 
 212                     fullmsg 
+= u
' ' * (prev_len 
- len(fullmsg
)) 
 213                 self
._report
_progress
_prev
_line
_length 
= len(fullmsg
) 
 216                 clear_line 
= (u
'\r\x1b[K' if sys
.stderr
.isatty() else u
'\r') 
 217             self
.to_screen(clear_line 
+ fullmsg
, skip_eol
=not is_last_line
) 
 218         self
.to_console_title(u
'youtube-dl ' + msg
) 
 220     def report_progress(self
, percent
, data_len_str
, speed
, eta
): 
 221         """Report download progress.""" 
 222         if self
.params
.get('noprogress', False): 
 225             eta_str 
= self
.format_eta(eta
) 
 227             eta_str 
= 'Unknown ETA' 
 228         if percent 
is not None: 
 229             percent_str 
= self
.format_percent(percent
) 
 231             percent_str 
= 'Unknown %' 
 232         speed_str 
= self
.format_speed(speed
) 
 234         msg 
= (u
'%s of %s at %s ETA %s' % 
 235                (percent_str
, data_len_str
, speed_str
, eta_str
)) 
 236         self
._report
_progress
_status
(msg
) 
 238     def report_progress_live_stream(self
, downloaded_data_len
, speed
, elapsed
): 
 239         if self
.params
.get('noprogress', False): 
 241         downloaded_str 
= format_bytes(downloaded_data_len
) 
 242         speed_str 
= self
.format_speed(speed
) 
 243         elapsed_str 
= FileDownloader
.format_seconds(elapsed
) 
 244         msg 
= u
'%s at %s (%s)' % (downloaded_str
, speed_str
, elapsed_str
) 
 245         self
._report
_progress
_status
(msg
) 
 247     def report_finish(self
, data_len_str
, tot_time
): 
 248         """Report download finished.""" 
 249         if self
.params
.get('noprogress', False): 
 250             self
.to_screen(u
'[download] Download completed') 
 252             self
._report
_progress
_status
( 
 253                 (u
'100%% of %s in %s' % 
 254                  (data_len_str
, self
.format_seconds(tot_time
))), 
 257     def report_resuming_byte(self
, resume_len
): 
 258         """Report attempt to resume at given byte.""" 
 259         self
.to_screen(u
'[download] Resuming download at byte %s' % resume_len
) 
 261     def report_retry(self
, count
, retries
): 
 262         """Report retry in case of HTTP error 5xx""" 
 263         self
.to_screen(u
'[download] Got server HTTP error. Retrying (attempt %d of %d)...' % (count
, retries
)) 
 265     def report_file_already_downloaded(self
, file_name
): 
 266         """Report file has already been fully downloaded.""" 
 268             self
.to_screen(u
'[download] %s has already been downloaded' % file_name
) 
 269         except UnicodeEncodeError: 
 270             self
.to_screen(u
'[download] The file has already been downloaded') 
 272     def report_unable_to_resume(self
): 
 273         """Report it was impossible to resume download.""" 
 274         self
.to_screen(u
'[download] Unable to resume') 
 276     def download(self
, filename
, info_dict
): 
 277         """Download to a filename using the info from info_dict 
 278         Return True on success and False otherwise 
 280         # Check file already present 
 281         if self
.params
.get('continuedl', False) and os
.path
.isfile(encodeFilename(filename
)) and not self
.params
.get('nopart', False): 
 282             self
.report_file_already_downloaded(filename
) 
 283             self
._hook
_progress
({ 
 284                 'filename': filename
, 
 285                 'status': 'finished', 
 286                 'total_bytes': os
.path
.getsize(encodeFilename(filename
)), 
 290         return self
.real_download(filename
, info_dict
) 
 292     def real_download(self
, filename
, info_dict
): 
 293         """Real download process. Redefine in subclasses.""" 
 294         raise NotImplementedError(u
'This method must be implemented by sublcasses') 
 296     def _hook_progress(self
, status
): 
 297         for ph 
in self
._progress
_hooks
: 
 300     def add_progress_hook(self
, ph
): 
 301         """ ph gets called on download progress, with a dictionary with the entries 
 302         * filename: The final filename 
 303         * status: One of "downloading" and "finished" 
 305         It can also have some of the following entries: 
 307         * downloaded_bytes: Bytes on disks 
 308         * total_bytes: Total bytes, None if unknown 
 309         * tmpfilename: The filename we're currently writing to 
 310         * eta: The estimated time in seconds, None if unknown 
 311         * speed: The download speed in bytes/second, None if unknown 
 313         Hooks are guaranteed to be called at least once (with status "finished") 
 314         if the download is successful. 
 316         self
._progress
_hooks
.append(ph
)