95 lines
3.5 KiB
Python
95 lines
3.5 KiB
Python
|
|
import os
|
|
import re
|
|
import requests
|
|
|
|
USER_AGENT = "VLC/3.0.9 LibVLC/3.0.9"
|
|
HEADERS = {'User-Agent': USER_AGENT}
|
|
|
|
def sanitize_filename(name):
|
|
return re.sub(r'[\\/*?:\"<>|]', "", name).strip()
|
|
|
|
def aria2c_exists():
|
|
return os.path.exists("./aria2c") or os.path.exists("./aria2c.exe")
|
|
|
|
def get_remote_file_size(url):
|
|
try:
|
|
with requests.head(url, headers=HEADERS, timeout=10) as head:
|
|
head.raise_for_status()
|
|
content_length = head.headers.get('content-length')
|
|
return int(content_length) if content_length else 0
|
|
except Exception:
|
|
return 0
|
|
|
|
def download_with_aria2c(url, dest_path, max_connections=1):
|
|
import subprocess
|
|
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
|
remote_size = get_remote_file_size(url)
|
|
if os.path.exists(dest_path):
|
|
local_size = os.path.getsize(dest_path)
|
|
if remote_size > 0 and local_size >= remote_size:
|
|
print(f"✔ Already downloaded: {os.path.basename(dest_path)}")
|
|
return
|
|
aria2_bin = "aria2c.exe" if os.name == 'nt' else "./aria2c"
|
|
cmd = [
|
|
aria2_bin,
|
|
"--dir", os.path.dirname(dest_path),
|
|
"--out", os.path.basename(dest_path),
|
|
"--max-connection-per-server=1",
|
|
"--split=1",
|
|
"--min-split-size=1M",
|
|
"--console-log-level=warn",
|
|
"--allow-overwrite=true",
|
|
"--header", f"User-Agent: {USER_AGENT}",
|
|
"--max-concurrent-downloads", str(max_connections),
|
|
url
|
|
]
|
|
print(f"\n⚡ Fast downloading with aria2c: {os.path.basename(dest_path)}")
|
|
try:
|
|
subprocess.run(cmd, check=True)
|
|
except subprocess.CalledProcessError:
|
|
print(f"⚠ aria2c failed. Falling back to Python downloader.")
|
|
download_file_with_progress(url, dest_path, os.path.basename(dest_path))
|
|
|
|
def download_file_with_progress(url, dest_path, title):
|
|
resume_byte_pos = 0
|
|
local_exists = os.path.exists(dest_path)
|
|
mode = 'wb'
|
|
headers = HEADERS.copy()
|
|
try:
|
|
with requests.head(url, headers=headers, timeout=10) as head:
|
|
head.raise_for_status()
|
|
content_length = head.headers.get('content-length')
|
|
remote_size = int(content_length) if content_length else 0
|
|
accepts_ranges = head.headers.get('accept-ranges', 'none') == 'bytes'
|
|
except Exception:
|
|
remote_size = 0
|
|
accepts_ranges = False
|
|
|
|
if local_exists:
|
|
local_size = os.path.getsize(dest_path)
|
|
if remote_size > 0 and local_size >= remote_size:
|
|
print(f"✔ Already downloaded: {title}")
|
|
return
|
|
elif remote_size > 0 and local_size < remote_size and accepts_ranges:
|
|
resume_byte_pos = local_size
|
|
headers['Range'] = f'bytes={resume_byte_pos}-'
|
|
mode = 'ab'
|
|
print(f"⏯ Resuming download for {title} at {local_size / (1024 ** 2):.2f} MB")
|
|
else:
|
|
print(f"⚠ Restarting download for {title}")
|
|
mode = 'wb'
|
|
|
|
with requests.get(url, headers=headers, stream=True, timeout=30) as response:
|
|
response.raise_for_status()
|
|
total = remote_size
|
|
downloaded = resume_byte_pos
|
|
with open(dest_path, mode) as f:
|
|
for chunk in response.iter_content(chunk_size=1024 * 1024):
|
|
if chunk:
|
|
f.write(chunk)
|
|
downloaded += len(chunk)
|
|
percent = (downloaded / total * 100) if total else 0
|
|
print(f"\rDownloading '{title}': {percent:.2f}%", end='', flush=True)
|
|
print(f"\n✔ Download complete: {dest_path}")
|