dfslkjelf
This commit is contained in:
124
SYS/download.py
124
SYS/download.py
@@ -510,6 +510,7 @@ def _download_direct_file(
|
||||
output_dir: Path,
|
||||
debug_logger: Optional[DebugLogger] = None,
|
||||
quiet: bool = False,
|
||||
suggested_filename: Optional[str] = None,
|
||||
) -> DownloadMediaResult:
|
||||
"""Download a direct file (PDF, image, document, etc.) without yt-dlp."""
|
||||
ensure_directory(output_dir)
|
||||
@@ -517,6 +518,44 @@ def _download_direct_file(
|
||||
from urllib.parse import unquote, urlparse, parse_qs
|
||||
import re
|
||||
|
||||
def _sanitize_filename(name: str) -> str:
|
||||
# Windows-safe filename sanitization.
|
||||
# Keep it simple: strip path parts, drop invalid chars, collapse whitespace.
|
||||
text = str(name or "").strip()
|
||||
if not text:
|
||||
return ""
|
||||
# Remove any path components
|
||||
text = text.replace("/", "\\")
|
||||
text = text.split("\\")[-1]
|
||||
|
||||
invalid = set('<>:"/\\|?*')
|
||||
cleaned_chars: List[str] = []
|
||||
for ch in text:
|
||||
o = ord(ch)
|
||||
if o < 32:
|
||||
cleaned_chars.append(" ")
|
||||
continue
|
||||
if ch in invalid:
|
||||
cleaned_chars.append(" ")
|
||||
continue
|
||||
cleaned_chars.append(ch)
|
||||
cleaned = " ".join("".join(cleaned_chars).split()).strip()
|
||||
# Avoid trailing dots/spaces on Windows
|
||||
cleaned = cleaned.rstrip(" .")
|
||||
return cleaned
|
||||
|
||||
def _unique_path(path: Path) -> Path:
|
||||
if not path.exists():
|
||||
return path
|
||||
stem = path.stem
|
||||
suffix = path.suffix
|
||||
parent = path.parent
|
||||
for i in range(1, 10_000):
|
||||
candidate = parent / f"{stem} ({i}){suffix}"
|
||||
if not candidate.exists():
|
||||
return candidate
|
||||
return parent / f"{stem} ({int(time.time())}){suffix}"
|
||||
|
||||
# Extract filename from URL
|
||||
parsed_url = urlparse(url)
|
||||
url_path = parsed_url.path
|
||||
@@ -560,11 +599,29 @@ def _download_direct_file(
|
||||
if not quiet:
|
||||
log(f"Could not get filename from headers: {e}", file=sys.stderr)
|
||||
|
||||
# Fallback if we still don't have a good filename
|
||||
# Apply suggested filename (from provider title) if given.
|
||||
suggested = _sanitize_filename(suggested_filename) if suggested_filename else ""
|
||||
if suggested:
|
||||
# Preserve extension from suggested name if present; otherwise borrow from detected filename.
|
||||
suggested_path = Path(suggested)
|
||||
if suggested_path.suffix:
|
||||
filename = suggested
|
||||
else:
|
||||
detected_ext = ""
|
||||
try:
|
||||
detected_ext = Path(str(filename)).suffix
|
||||
except Exception:
|
||||
detected_ext = ""
|
||||
if detected_ext:
|
||||
filename = suggested + detected_ext
|
||||
else:
|
||||
filename = suggested
|
||||
|
||||
# Final fallback if we still don't have a good filename
|
||||
if not filename or "." not in filename:
|
||||
filename = "downloaded_file.bin"
|
||||
|
||||
file_path = output_dir / filename
|
||||
file_path = _unique_path(output_dir / filename)
|
||||
progress_bar = ProgressBar()
|
||||
|
||||
if not quiet:
|
||||
@@ -581,32 +638,57 @@ def _download_direct_file(
|
||||
total_bytes[0] = content_length
|
||||
|
||||
now = time.time()
|
||||
if now - last_progress_time[0] >= 0.5 and total_bytes[0] > 0:
|
||||
elapsed = now - start_time
|
||||
percent = (bytes_downloaded / content_length) * 100 if content_length > 0 else 0
|
||||
speed = bytes_downloaded / elapsed if elapsed > 0 else 0
|
||||
eta_seconds = (content_length - bytes_downloaded) / speed if speed > 0 else 0
|
||||
if now - last_progress_time[0] < 0.5:
|
||||
return
|
||||
|
||||
speed_str = progress_bar.format_bytes(speed) + "/s"
|
||||
minutes, seconds = divmod(int(eta_seconds), 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
eta_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
elapsed = now - start_time
|
||||
percent = (bytes_downloaded / content_length) * 100 if content_length > 0 else 0
|
||||
speed = bytes_downloaded / elapsed if elapsed > 0 else 0
|
||||
eta_str: Optional[str] = None
|
||||
if content_length > 0 and speed > 0:
|
||||
try:
|
||||
eta_seconds = max(0.0, float(content_length - bytes_downloaded) / float(speed))
|
||||
minutes, seconds = divmod(int(eta_seconds), 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
eta_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
except Exception:
|
||||
eta_str = None
|
||||
|
||||
progress_line = progress_bar.format_progress(
|
||||
percent_str=f"{percent:.1f}%",
|
||||
downloaded=bytes_downloaded,
|
||||
total=content_length,
|
||||
speed_str=speed_str,
|
||||
eta_str=eta_str,
|
||||
)
|
||||
if not quiet:
|
||||
debug(progress_line)
|
||||
last_progress_time[0] = now
|
||||
speed_str = progress_bar.format_bytes(speed) + "/s"
|
||||
|
||||
progress_line = progress_bar.format_progress(
|
||||
percent_str=f"{percent:.1f}%",
|
||||
downloaded=bytes_downloaded,
|
||||
total=content_length if content_length > 0 else None,
|
||||
speed_str=speed_str,
|
||||
eta_str=eta_str,
|
||||
)
|
||||
|
||||
if not quiet:
|
||||
try:
|
||||
if getattr(sys.stderr, "isatty", lambda: False)():
|
||||
sys.stderr.write("\r" + progress_line + " ")
|
||||
sys.stderr.flush()
|
||||
else:
|
||||
# Non-interactive: print occasional progress lines.
|
||||
log(progress_line, file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
last_progress_time[0] = now
|
||||
|
||||
with HTTPClient(timeout=30.0) as client:
|
||||
client.download(url, str(file_path), progress_callback=progress_callback)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
# Clear in-place progress bar.
|
||||
if not quiet:
|
||||
try:
|
||||
if getattr(sys.stderr, "isatty", lambda: False)():
|
||||
sys.stderr.write("\r" + (" " * 140) + "\r")
|
||||
sys.stderr.flush()
|
||||
except Exception:
|
||||
pass
|
||||
avg_speed_str = progress_bar.format_bytes(downloaded_bytes[0] / elapsed if elapsed > 0 else 0) + "/s"
|
||||
if not quiet:
|
||||
debug(f"✓ Downloaded in {elapsed:.1f}s at {avg_speed_str}")
|
||||
|
||||
Reference in New Issue
Block a user