diff --git a/media-webgui/app.py b/media-webgui/app.py index 3b0b272..4ad43c4 100644 --- a/media-webgui/app.py +++ b/media-webgui/app.py @@ -18,7 +18,7 @@ from urllib.parse import urlparse import paramiko from fastapi import FastAPI, Form, Request -from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.staticfiles import StaticFiles OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "/output").rstrip("/") @@ -97,6 +97,7 @@ class Job: library: str proxy: str headers: List[str] + progress: float status: str message: str @@ -162,7 +163,6 @@ def proxy_is_usable(proxy: str) -> bool: return False - def format_proxy_lines(raw: str, scheme: str) -> str: scheme = scheme.strip().lower() if scheme not in {"socks5", "socks4", "http", "https"}: @@ -234,24 +234,6 @@ refresh_proxies() threading.Thread(target=proxy_refresh_loop, daemon=True).start() -def parse_header_lines(raw: str) -> List[str]: - headers = [] - for line in (raw or "").splitlines(): - s = line.strip() - if not s or s.startswith("#"): - continue - if ":" not in s: - raise ValueError(f"Invalid header line: {s}") - name, value = s.split(":", 1) - name = name.strip() - value = value.strip() - if not name or not value: - raise ValueError(f"Invalid header line: {s}") - headers.append(f"{name}: {value}") - return headers - - - def parse_header_lines(raw: str) -> List[str]: headers = [] for line in (raw or "").splitlines(): @@ -281,20 +263,49 @@ def pick_engine(url: str, forced: str) -> str: return "direct" -def run_ytdlp(url: str, out_dir: str, fmt: str, proxy: str): - cmd = ["yt-dlp", "-f", fmt, "-o", f"{out_dir}/%(title)s.%(ext)s", url] +def run_ytdlp(url: str, out_dir: str, fmt: str, proxy: str, progress_cb): + cmd = ["yt-dlp", "--newline", "-f", fmt, "-o", f"{out_dir}/%(title)s.%(ext)s", url] if proxy: cmd += ["--proxy", proxy] - subprocess.check_call(cmd) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + if not proc.stdout: + raise RuntimeError("yt-dlp failed to start") + progress_re = re.compile(r"\\[download\\]\\s+([\\d.]+)%") + for line in proc.stdout: + match = progress_re.search(line) + if match: + progress_cb(float(match.group(1))) + ret = proc.wait() + if ret != 0: + raise subprocess.CalledProcessError(ret, cmd) -def run_aria2(url: str, out_dir: str, proxy: str, headers: List[str] | None = None): - cmd = ["aria2c", "--dir", out_dir, "--allow-overwrite=true", "--auto-file-renaming=false", url] +def run_aria2(url: str, out_dir: str, proxy: str, progress_cb, headers: List[str] | None = None): + cmd = [ + "aria2c", + "--dir", + out_dir, + "--allow-overwrite=true", + "--auto-file-renaming=false", + "--summary-interval=1", + url, + ] if proxy: cmd += ["--all-proxy", proxy] for header in headers or []: cmd += ["--header", header] - subprocess.check_call(cmd) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + if not proc.stdout: + raise RuntimeError("aria2c failed to start") + percent_re = re.compile(r"\\((\\d+)%\\)") + for line in proc.stdout: + match = percent_re.search(line) + if match: + progress_cb(float(match.group(1))) + ret = proc.wait() + if ret != 0: + raise subprocess.CalledProcessError(ret, cmd) + def md5_file(path: str) -> str: @@ -409,6 +420,18 @@ def worker(jobid: str): header_note = f"Headers={len(headers)}" if headers else "Headers=none" job.status = "downloading" job.message = f"Engine={engine} Proxy={'none' if not proxy else proxy} {header_note}" + job.progress = 0.0 + + def update_progress(value: float): + with lock: + jobs[jobid].progress = max(0.0, min(100.0, value)) + + if engine == "ytdlp": + run_ytdlp(job.url, OUTPUT_DIR, YTDLP_FORMAT, proxy, update_progress) + elif engine == "hoster": + run_aria2(job.url, OUTPUT_DIR, proxy, update_progress, headers=headers) + else: + run_aria2(job.url, OUTPUT_DIR, proxy, update_progress) if engine == "ytdlp": run_ytdlp(job.url, OUTPUT_DIR, YTDLP_FORMAT, proxy) @@ -459,6 +482,7 @@ def worker(jobid: str): with lock: job.status = "finished" + job.progress = 100.0 job.message = f"OK ({len(new_files)} file(s))" except Exception as e: @@ -488,6 +512,9 @@ def render_downloads(error: str = "") -> str: f"
| JobID | URL | Engine | Library | Proxy | Progress | Status |
|---|---|---|---|---|---|---|
| No jobs yet. | ||||||