import sys, os, time, ssl, gzip, multiprocessing
from io import BytesIO
try:
from urllib.request import urlopen, Request
import queue as q
except ImportError:
import urllib2
from urllib2 import urlopen, Request
import Queue as q
TERMINAL_WIDTH = 120 if os.name=="nt" else 80
def get_size(size, suffix=None, use_1024=False, round_to=2, strip_zeroes=False):
if size == -1:
return "Unknown"
ext = ["B","KiB","MiB","GiB","TiB","PiB"] if use_1024 else ["B","KB","MB","GB","TB","PB"]
div = 1024 if use_1024 else 1000
s = float(size)
s_dict = {}
for e in ext:
s_dict[e] = s
s /= div
suffix = next((x for x in ext if x.lower() == suffix.lower()),None) if suffix else suffix
biggest = suffix if suffix else next((x for x in ext[::-1] if s_dict[x] >= 1), "B")
try:round_to=int(round_to)
except:round_to=2
round_to = 0 if round_to < 0 else 15 if round_to > 15 else round_to
bval = round(s_dict[biggest], round_to)
a,b = str(bval).split(".")
b = b.rstrip("0") if strip_zeroes else b.ljust(round_to,"0") if round_to > 0 else ""
return "{:,}{} {}".format(int(a),"" if not b else "."+b,biggest)
def _process_hook(queue, total_size, bytes_so_far=0, update_interval=1.0, max_packets=0):
packets = []
speed = remaining = ""
last_update = time.time()
while True:
if total_size > 0:
percent = float(bytes_so_far) / total_size
percent = round(percent*100, 2)
t_s = get_size(total_size)
try:
b_s = get_size(bytes_so_far, t_s.split(" ")[1])
except:
b_s = get_size(bytes_so_far)
perc_str = " {:.2f}%".format(percent)
bar_width = (TERMINAL_WIDTH // 3)-len(perc_str)
progress = "=" * int(bar_width * (percent/100))
sys.stdout.write("\r\033[K{}/{} | {}{}{}{}{}".format(
b_s,
t_s,
progress,
" " * (bar_width-len(progress)),
perc_str,
speed,
remaining
))
else:
b_s = get_size(bytes_so_far)
sys.stdout.write("\r\033[K{}{}".format(b_s, speed))
sys.stdout.flush()
try:
packet = queue.get(timeout=update_interval)
if packet == "DONE":
print("")
return
packets.append(packet)
if max_packets > 0:
packets = packets[-max_packets:]
bytes_so_far += packet[1]
except q.Empty:
packets = []
speed = " | 0 B/s"
remaining = " | ?? left" if total_size > 0 else ""
except KeyboardInterrupt:
print("")
return
update_check = time.time()
if packets and update_check - last_update >= update_interval:
last_update = update_check
speed = " | ?? B/s"
if len(packets) > 1:
try:
first,last = packets[0][0],packets[-1][0]
chunks = sum([float(x[1]) for x in packets])
t = last-first
assert t >= 0
bytes_speed = 1. / t * chunks
speed = " | {}/s".format(get_size(bytes_speed,round_to=1))
if total_size > 0:
seconds_left = (total_size-bytes_so_far) / bytes_speed
days = seconds_left // 86400
hours = (seconds_left - (days*86400)) // 3600
mins = (seconds_left - (days*86400) - (hours*3600)) // 60
secs = seconds_left - (days*86400) - (hours*3600) - (mins*60)
if days > 99 or bytes_speed == 0:
remaining = " | ?? left"
else:
remaining = " | {}{:02d}:{:02d}:{:02d} left".format(
"{}:".format(int(days)) if days else "",
int(hours),
int(mins),
int(round(secs))
)
except:
pass
packets = []
class Downloader:
def __init__(self,**kwargs):
self.ua = kwargs.get("useragent",{"User-Agent":"Mozilla"})
self.chunk = 1048576
if os.name=="nt": os.system("color")
cafile = ssl.get_default_verify_paths().openssl_cafile
try:
if not os.path.exists(cafile):
import certifi
cafile = certifi.where()
self.ssl_context = ssl.create_default_context(cafile=cafile)
except:
self.ssl_context = ssl._create_unverified_context()
return
def _decode(self, value, encoding="utf-8", errors="ignore"):
if sys.version_info >= (3,0) and isinstance(value, bytes):
return value.decode(encoding,errors)
return value
def _update_main_name(self):
try:
path = os.path.abspath(sys.modules["__main__"].__file__)
except AttributeError as e:
return None
if not os.path.isfile(path):
return None
name = os.path.basename(path).lower()
fldr = os.path.dirname(path)
for f in os.listdir(fldr):
if f.lower() == name:
new_path = os.path.join(fldr,f)
sys.modules["__main__"].__file__ = new_path
return new_path
return None
def _get_headers(self, headers = None):
target = headers if isinstance(headers,dict) else self.ua
new_headers = {}
for k in target:
new_headers[k] = target[k]
return new_headers
def open_url(self, url, headers = None):
headers = self._get_headers(headers)
try:
response = urlopen(Request(url, headers=headers), context=self.ssl_context)
except Exception as e:
return None
return response
def get_size(self, *args, **kwargs):
return get_size(*args,**kwargs)
def get_string(self, url, progress = True, headers = None, expand_gzip = True):
response = self.get_bytes(url,progress,headers,expand_gzip)
if response is None: return None
return self._decode(response)
def get_bytes(self, url, progress = True, headers = None, expand_gzip = True):
response = self.open_url(url, headers)
if response is None: return None
try: total_size = int(response.headers['Content-Length'])
except: total_size = -1
chunk_so_far = b""
packets = queue = process = None
if progress:
packets = [] if progress else None
queue = multiprocessing.Queue()
process = multiprocessing.Process(
target=_process_hook,
args=(queue,total_size)
)
process.daemon = True
if os.name == "nt" and hasattr(multiprocessing,"forking"):
self._update_main_name()
process.start()
try:
while True:
chunk = response.read(self.chunk)
if progress:
queue.put((time.time(),len(chunk)))
if not chunk: break
chunk_so_far += chunk
finally:
response.close()
if expand_gzip and response.headers.get("Content-Encoding","unknown").lower() == "gzip":
fileobj = BytesIO(chunk_so_far)
gfile = gzip.GzipFile(fileobj=fileobj)
return gfile.read()
if progress:
queue.put("DONE")
process.join()
return chunk_so_far
def stream_to_file(self, url, file_path, progress = True, headers = None, ensure_size_if_present = True, allow_resume = False):
response = self.open_url(url, headers)
if response is None: return None
bytes_so_far = 0
try: total_size = int(response.headers['Content-Length'])
except: total_size = -1
packets = queue = process = None
mode = "wb"
if allow_resume and os.path.isfile(file_path) and total_size != -1:
current_size = os.stat(file_path).st_size
if current_size == total_size:
return file_path
elif current_size < total_size:
response.close()
bytes_so_far = current_size
mode = "ab"
new_headers = self._get_headers(headers)
byte_string = "bytes={}-".format(current_size)
new_headers["Range"] = byte_string
response = self.open_url(url, new_headers)
if response is None: return None
if progress:
packets = [] if progress else None
queue = multiprocessing.Queue()
process = multiprocessing.Process(
target=_process_hook,
args=(queue,total_size,bytes_so_far)
)
process.daemon = True
if os.name == "nt" and hasattr(multiprocessing,"forking"):
self._update_main_name()
process.start()
with open(file_path,mode) as f:
try:
while True:
chunk = response.read(self.chunk)
bytes_so_far += len(chunk)
if progress:
queue.put((time.time(),len(chunk)))
if not chunk: break
f.write(chunk)
finally:
response.close()
if progress:
queue.put("DONE")
process.join()
if ensure_size_if_present and total_size != -1:
if bytes_so_far != total_size:
return None
return file_path if os.path.exists(file_path) else None