Files
XCDownloader/common.py
2025-04-25 12:12:02 +00:00

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}")