Skip to content
Conduit

mcp server

VideoDownLoad

VideoDownLoad

AiForSoftware/VideoDownLoadcommunitylistedunreviewed

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, 2026

VideoDownLoad · 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

FeatureDescription
Multi-platform parsingDouyin, Bilibili, YouTube and Tencent Video (v.qq.com) parsers; WebMediaGrabber as a generic web-media fallback
Quality selection4K / 1080P+ / 1080P / 720P / 480P … enumerated as separate entries, grouped in the UI
Download orchestrationConcurrent downloads (configurable "simultaneous downloads"), task-level pause / resume / cancel
Subtitle muxingSubtitle tracks are downloaded and muxed in automatically: Bilibili proprietary JSON→VTT, YouTube captionTracks, HLS (m3u8) auto-extraction
Login stateTop-bar "Login" button opens a DrissionPage window; cookies are stored per platform in per_source_cookies
Self-healing startupSupervisor + UI child process; a hung WebView2 init is retried (up to 5×) and the frozen attempt is never shown to the user
Single instanceRe-opening focuses the existing window and shows a reminder — it never kills a launch in progress
Bundled toolsN_m3u8DL-RE (HLS/m3u8) and aria2c (multi-threaded) ship with the package; ffmpeg/ffprobe come from the system PATH
Version managementThe version auto-increments on every build (version.txt is the single source of truth) and is used for install/activation reporting
MultilingualChinese / 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 guidanceWhen 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-RE and aria2c are 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.ps1 first (build.spec already collects the mcp dependency); 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_url does 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)

GroupToolsPurpose
Discoverylist_sourcesParsers + engine state + ffmpeg/node availability
Parseparse_url / parse_urlsURL(s) → quality items (each with a unique key)
DownloaddownloadSubmit keys → job_id
Downloadget_download_statePure snapshot: returns immediately; poll until terminal status
Downloadpause_job / resume_job / cancel_job / retry_audio / clear_jobsJob control
Configget_config / set_configwork_dir, concurrency, proxy, cookies, default quality
Historyget_history / clear_historyParse history

Known boundaries

  • Platform login needs the desktop GUI popup — sign in from the desktop app once; the MCP server then shares the stored per_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_state is 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

KeyMeaning
work_dirdownload output directory
concurrent_downloadsnumber of simultaneous downloads (thread pool size)
default_qualitybest / 4k / 1080p / 720p / 480p / 360p / auto
download_subtitlesdownload subtitles and mux them into the video
allowed_sourceslist of enabled parser class names
per_source_cookiesper-platform login state (written by the login dialog)
proxyproxy (host:port, empty = direct)
languageUI 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 the fl.fi list returned by the API (commonly 480P / 720P, depends on the title — 1080P is not guaranteed). Single-segment sources only: multi-segment sources (fn like a.p201.1.mp4) are not stitched and fall back to WebMediaGrabber.
  • 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.
↑↓ navigate openesc close