aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rwxr-xr-xbunkr_dl27
2 files changed, 29 insertions, 0 deletions
diff --git a/README.md b/README.md
index d643c5a..5166c6e 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,8 @@ search, from the command line.
- Resumes partially downloaded files when run again against the same folder.
- Shows a progress bar for the album (files) and for the current file
(size, speed, ETA).
+- Prints a summary at the end, also on Ctrl+C: files downloaded, skipped,
+ failed and partial, bytes written, time and average speed.
- Works with any Bunkr mirror domain, since links are resolved relative to the
URL you pass.
diff --git a/bunkr_dl b/bunkr_dl
index 36f9264..9dd7621 100755
--- a/bunkr_dl
+++ b/bunkr_dl
@@ -20,6 +20,7 @@ import requests
from bs4 import BeautifulSoup
from rich.console import Console, Group
from rich.live import Live
+from rich import filesize
from rich.progress import (Progress, TextColumn, BarColumn, MofNCompleteColumn,
TimeElapsedColumn, TaskProgressColumn, DownloadColumn,
TransferSpeedColumn, TimeRemainingColumn)
@@ -28,6 +29,7 @@ import os
import sys
import argparse
import time
+from datetime import timedelta
from urllib.parse import urljoin, urlparse, quote
DEFAULT_OUTPUT = "/data/bunkr" # Base download folder, override with -o/--output
@@ -45,6 +47,8 @@ album_bars = Progress(TextColumn("{task.description:30}"), BarColumn(complete_st
file_bars = Progress(TextColumn("{task.description:30}"), BarColumn(complete_style="cyan"),
TaskProgressColumn(), DownloadColumn(binary_units=True), TransferSpeedColumn(),
TimeRemainingColumn(), console=console)
+# Run totals for the summary; bytes counts only what this run wrote
+stats = dict(downloaded=0, skipped=0, failed=0, partial=0, bytes=0)
def get_soup(url):
"""Helper function to get parsed HTML"""
@@ -165,6 +169,7 @@ def download_file(file_url, file_name, save_dir, retries=5):
with requests.get(file_url, headers=req_headers, stream=True, timeout=60) as response:
if response.status_code == 416: # Range past end: already complete
console.out(f"Already downloaded: {local_filename}")
+ stats['skipped'] += 1
return local_filename
response.raise_for_status()
# Check if it's a resumable download
@@ -183,9 +188,14 @@ def download_file(file_url, file_name, save_dir, retries=5):
if chunk: # Filter out keep-alive chunks
f.write(chunk)
file_bars.advance(task, len(chunk))
+ stats['bytes'] += len(chunk)
+ except KeyboardInterrupt:
+ stats['partial'] += 1 # Kept on disk, a rerun resumes it
+ raise
finally:
file_bars.remove_task(task)
console.out(f"Download complete: {local_filename}")
+ stats['downloaded'] += 1
return local_filename
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError, requests.exceptions.Timeout) as e:
attempt += 1
@@ -196,6 +206,7 @@ def download_file(file_url, file_name, save_dir, retries=5):
break
console.out(f"Failed to download {file_url} after {retries} attempts.")
+ stats['failed'] += 1
return None
def process_album(album_url, base_save_directory):
@@ -239,8 +250,10 @@ def process_album(album_url, base_save_directory):
download_file(*final_media, album_save_directory)
else:
console.out(f"Failed to get final media from {download_link}")
+ stats['failed'] += 1
else:
console.out(f"Failed to find download link on {media_link}")
+ stats['failed'] += 1
album_bars.advance(task)
def main():
@@ -295,11 +308,25 @@ def main():
console.out(f"Processing album {url}")
process_album(url, save_directory)
+def print_summary(status, elapsed):
+ speed = filesize.decimal(int(stats['bytes'] / elapsed)) if elapsed else "0 bytes"
+ console.out(f"{status}: {stats['downloaded']} downloaded, {stats['skipped']} skipped, "
+ f"{stats['failed']} failed, {stats['partial']} partial, "
+ f"{filesize.decimal(stats['bytes'])} in {timedelta(seconds=int(elapsed))} "
+ f"(avg {speed}/s)")
+
if __name__ == "__main__":
+ start = time.monotonic()
+ status = "Done"
try:
with Live(Group(album_bars, file_bars), console=console):
main()
except KeyboardInterrupt:
# Partial file stays on disk; rerunning the same command resumes it
console.out("\nInterrupted. Rerun the same command to resume.")
+ status = "Stopped"
sys.exit(130)
+ finally:
+ # Skip it for --help, bad arguments and runs that found nothing
+ if any(stats.values()):
+ print_summary(status, time.monotonic() - start)