Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 25, 2026VideoDownLoad · Universal Video Downloader (Windows)
A Windows desktop video downloader: it wraps the command-line download engine vd in a
GUI shell (pywebview + Edge WebView2) and supports parsing, quality selection, concurrent
downloading and task-level pause/resume for Douyin / Bilibili / YouTube / Tencent Video,
plus automatic subtitle download and muxing into the video. The UI ships in
Chinese and English (Chinese by default; a language picker is shown on first launch).
🤖 AI-Generated Project Notice
This project was generated by AI.
The bulk of the code, documentation and build scripts was produced and iterated by an AI (CodeBuddy) under human guidance; humans provided the requirements, verified the results and signed off on them.
Consequences: expect some atypical code and defensive logic that looks redundant — most of it guards against a specific historical incident.
✨ Features
| Feature | Description |
|---|---|
| Multi-platform parsing | Douyin, Bilibili, YouTube and Tencent Video (v.qq.com) parsers; WebMediaGrabber as a generic web-media fallback |
| Quality selection | 4K / 1080P+ / 1080P / 720P / 480P … enumerated as separate entries, grouped in the UI |
| Download orchestration | Concurrent downloads (configurable "simultaneous downloads"), task-level pause / resume / cancel |
| Subtitle muxing | Subtitle tracks are downloaded and muxed in automatically: Bilibili proprietary JSON→VTT, YouTube captionTracks, HLS (m3u8) auto-extraction |
| Login state | Top-bar "Login" button opens a DrissionPage window; cookies are stored per platform in per_source_cookies |
| Self-healing startup | Supervisor + UI child process; a hung WebView2 init is retried (up to 5×) and the frozen attempt is never shown to the user |
| Single instance | Re-opening focuses the existing window and shows a reminder — it never kills a launch in progress |
| Bundled tools | N_m3u8DL-RE (HLS/m3u8) and aria2c (multi-threaded) ship with the package; ffmpeg/ffprobe come from the system PATH |
| Version management | The version auto-increments on every build (version.txt is the single source of truth) and is used for install/activation reporting |
| Multilingual | Chinese / English (Chinese by default). Stored in config.language + localStorage['vd_lang']; the first launch (language empty) shows a one-time picker with the current language highlighted, and the top-bar globe icon switches it anytime |
| Parse-failure guidance | When no valid link is parsed on any platform, one clear prompt says "log in to this platform and retry" (top toast, 5s); the raw error stays available on hover instead of flooding the result card |
🏗 Architecture
Layers
┌──────────────────────────────────────────────────────────────┐
│ Frontend UI app/web/ index.html + app.js + styles.css │ rendered by Edge WebView2
│ (3 themes = CSS variable sets; adaptive poll) │
├──────────────────────────────────────────────────────────────┤
│ Bridge app/backend/api.py JsApi │ window.pywebview.api.*
│ ★ every attribute must be `_`-prefixed or │
│ pywebview walks the whole object graph and │
│ cold-start RSS climbs to several GB │
├──────────────────────────────────────────────────────────────┤
│ Service app/backend/core.py VideoDlService │ config / job queue /
│ │ lazy engine load / orchestration
├──────────────────────────────────────────────────────────────┤
│ Progress bus app/backend/progress.py ProgressBus │ hijacks rich Progress →
│ │ pause/cancel checkpoints (per job)
├──────────────────────────────────────────────────────────────┤
│ Engine engine/vd/ │ parse / download / subtitles / mux
│ ├─ vd.py VideoClient orchestration │
│ ├─ modules/sources/ platform parsers (lazy) │
│ ├─ modules/grabber.py WebMediaGrabber │
│ ├─ modules/utils/ chromium / youtubeutils … │
│ └─ modules/js/ non-python assets (datas) │
├──────────────────────────────────────────────────────────────┤
│ External tools ffmpeg / ffprobe (PATH), N_m3u8DL-RE, │
│ aria2c (bundled in bin/), node (optional) │
└──────────────────────────────────────────────────────────────┘
Process model
VideoDLDesktop.exe ← supervisor (owns the single-instance lock)
│ passes the "page loaded" flag-file path via env var
└── VideoDLDesktop.exe --child ← UI child process
starts hidden → writes the flag once loaded → shown via native ShowWindow
no flag before the timeout → psutil kills the whole subtree → retry (max 5)
- The first 2 attempts get 60s (cold start + Defender scan), later ones 30s.
- The liveness probe must fail twice in a row before retrying, so a legitimate cold start is never killed.
- On window close,
_hard_exit()recursively kills the process tree — "closed window" really means "process gone".
Data flow (one download)
Paste URL
→ JsApi.parse() → VideoDlService.parse()
→ lazy engine: the parser module is matched by hostname
(the module name must be a substring of the hostname)
→ parser returns a list of VideoInfo (one per quality, with the audio "set of five")
→ pick qualities in the UI → enqueue(): each entry is submitted to the thread pool
(max_workers = simultaneous downloads)
→ engine downloads (requests / curl_cffi; HLS via N_m3u8DL-RE; m3u8/mpd via ffmpeg)
→ ProgressBus reports progress per (job_id, item_key)
→ pause/cancel raise DownloadPaused / DownloadCancelled at the checkpoints
→ video + audio downloaded separately → ffmpeg mux (copy, then transcode fallback)
→ subtitles: download → convert if needed (Bilibili JSON→VTT) → mux with ffmpeg
→ output written to work_dir; unfinished jobs persist to jobs.json and can be resumed
after a restart
Directory layout
VideoDownLoad/
├── app/ # desktop shell
│ ├── app.py # entry point: --selftest / --child(UI) / default(supervisor)
│ ├── backend/
│ │ ├── core.py # VideoDlService: config, job queue, lazy engine, orchestration
│ │ ├── api.py # JsApi: every window.pywebview.api.* bridge method
│ │ ├── progress.py # ProgressBus: progress + pause/cancel checkpoints
│ │ ├── login.py # platform login via DrissionPage + cookie extraction
│ │ ├── diag.py # startup.log diagnostics (timing / memory / threads per line)
│ │ └── tracker.py # SoftwareTracker install/activation reporting client
│ ├── web/ # index.html + app.js + styles.css
│ ├── tools/ # smoke tests, memory measurement scripts
│ ├── pyinstaller_hooks/ # runtime_setup.py (node PATH, stdio fallback)
│ └── assets/ # icon, QR code
├── engine/vd/ # ★ video download engine; sys.path points at engine/
│ ├── vd.py # VideoClient: parsefromurl / download orchestration
│ └── modules/
│ ├── sources/ # platform parsers: douyin / bilibili / youtube
│ ├── grabber.py # WebMediaGrabber fallback
│ ├── utils/ # chromium (DrissionPage wrapper) / youtubeutils …
│ └── js/ # non-python decryption assets (shipped as datas)
├── bin/ # bundled tools: N_m3u8DL-RE.exe, aria2c.exe
├── build.spec # PyInstaller spec
├── build_now.ps1 # ★ the only supported build entry point
├── bump_version.py # version bump tool
├── version.txt # ★ single source of truth for the version (+1 patch per build)
├── requirements.txt # engine + desktop shell dependencies
└── dist/VideoDLDesktop/ # build output (onedir, the only release location)
🚀 Quick start
Requirements
- Windows 10 / 11 (x64)
- Python 3.11
- Microsoft Edge WebView2 Runtime
- ffmpeg / ffprobe on the system PATH (required for audio+video muxing)
N_m3u8DL-REandaria2care bundled — no separate install needed
Run from source
cd D:\CodeBuddy\VideoDownLoad
pip install -r requirements.txt # ★ install yt-dlp too, or audio muxing silently breaks
$env:PYTHONIOENCODING = "utf-8"
python app\app.py
Build
powershell -NoProfile -ExecutionPolicy Bypass -File build_now.ps1
Steps: ① bump the version → ② kill running instances → ③ pre-delete dist/build
→ ④ PyInstaller build → ⑤ print key log lines → ⑥ verify the artifact
(output in dist/VideoDLDesktop/).
Do not bypass the script with
python -m PyInstaller build.spec: the version would not be bumped and activation reporting would treat the new build as the same version.
Headless self-test
dist\VideoDLDesktop\VideoDLDesktop.exe --selftest
🔌 MCP Server (AI client integration)
The project ships a standard MCP (Model Context Protocol) stdio server, so any MCP-capable AI client (Claude Desktop, CodeBuddy, ...) can drive the downloader.
Starting the server
Prerequisite: Python 3.11 + pip install -r requirements.txt (mcp is listed in the desktop-shell section).
Option 1 — launched by the MCP client (recommended, the normal usage)
Just configure mcpServers in your client (see the example below) — the client
starts the server when the session begins and shuts it down afterwards.
There is no need, and it is discouraged, to run it manually in the background.
Option 2 — manual start from the command line (debugging)
.venv\Scripts\python.exe app\app.py --mcp
- No output / looking "stuck" is normal: in stdio mode the server waits for the MCP client to send JSON-RPC requests on stdin;
- Stop with
Ctrl+C, or the process exits automatically when the peer closes stdin.
Option 3 — packaged exe
dist\VideoDLDesktop\VideoDLDesktop.exe --mcp
Rebuild with
build_now.ps1first (build.specalready collects themcpdependency); stdio works when the client spawns the exe with pipes.
Verify the server works (send an initialize request; one JSON line back means OK):
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual","version":"0"}}}' | .venv\Scripts\python.exe app\app.py --mcp
Visual debugging (optional): the official MCP Inspector lets you call all 14 tools interactively:
npx @modelcontextprotocol/inspector .venv\Scripts\python.exe app\app.py --mcp
Runtime characteristics
- Headless reuse of
VideoDlService: no UI, no single-instance lock, no supervisor; - The engine is pre-warmed in the background at startup, so the first
parse_urldoes not pay the ~10s cold start; - stdin/stdout carry JSON-RPC only — all stray engine/child-process output is forwarded to stderr and never pollutes the protocol stream;
- Config and history are shared with the desktop app (
config.json/jobs.json).
Client configuration example
{
"mcpServers": {
"vd-downloader": {
"command": "python",
"args": ["D:\\CodeBuddy\\VideoDownLoad\\app\\app.py", "--mcp"],
"env": { "PYTHONIOENCODING": "utf-8" }
}
}
}
Tools (14)
| Group | Tools | Purpose |
|---|---|---|
| Discovery | list_sources | Parsers + engine state + ffmpeg/node availability |
| Parse | parse_url / parse_urls | URL(s) → quality items (each with a unique key) |
| Download | download | Submit keys → job_id |
| Download | get_download_state | Pure snapshot: returns immediately; poll until terminal status |
| Download | pause_job / resume_job / cancel_job / retry_audio / clear_jobs | Job control |
| Config | get_config / set_config | work_dir, concurrency, proxy, cookies, default quality |
| History | get_history / clear_history | Parse history |
Known boundaries
- Platform
loginneeds the desktop GUI popup — sign in from the desktop app once; the MCP server then shares the storedper_source_cookies; - The MCP server and the desktop app share the same config/job stores; avoid editing configuration from both at the same time;
get_download_stateis snapshot-only: poll it (every ~3-5s) until all item statuses are terminal (done / error / cancelled / paused).
⚙️ Configuration
Config file: C:\Users\<you>\AppData\Local\vd\vd-desktop\config.json
| Key | Meaning |
|---|---|
work_dir | download output directory |
concurrent_downloads | number of simultaneous downloads (thread pool size) |
default_quality | best / 4k / 1080p / 720p / 480p / 360p / auto |
download_subtitles | download subtitles and mux them into the video |
allowed_sources | list of enabled parser class names |
per_source_cookies | per-platform login state (written by the login dialog) |
proxy | proxy (host:port, empty = direct) |
language | UI language: zh-CN / en-US; empty = not chosen yet (a one-time picker is shown on first launch) |
Diagnostic logs live in the same directory — the first place to look when startup hangs:
Logs\startup.log(process-level events with timing / memory / threads per line)Logs\desktop.log(engine log)
⚠️ Known limitations
- No HTTP range resume: resuming re-submits the entry and the engine re-downloads it from the beginning.
- ffmpeg / ffprobe / node come from the system PATH; if missing, muxing or JS-based parsers will not work.
- YouTube applies temporary IP throttling (recovers over hours to days). During that window every tool — including yt-dlp and YoutubeDownloader — fails at once. This is a network state, not a code defect.
- Tencent Video: supports
v.qq.com/x/cover/...and/x/page/...; qualities are enumerated from thefl.filist returned by the API (commonly 480P / 720P, depends on the title — 1080P is not guaranteed). Single-segment sources only: multi-segment sources (fnlikea.p201.1.mp4) are not stitched and fall back toWebMediaGrabber. - Internal engine parse errors can only be surfaced as "please parse again"; the root cause lies in the engine's assumptions about site data structures.
📄 License
Released under the MIT License — see LICENSE.
MIT License
Copyright (c) 2026 VideoDownLoad contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- Third-party dependencies (pywebview, DrissionPage, yt-dlp, requests, curl_cffi, rich, PyInstaller, …) remain under their own licenses.
🙏 Credits & disclaimer
- For study and technical research only. Respect each platform's terms of service and the laws of your jurisdiction, and do not use downloaded content for commercial or infringing purposes.