A local-first media workflow toolkit for downloads, playlists, and automation through CLI and MCP interfaces
Project description
VDL — Local Media Workflow Toolkit
VDL is a local-first media workflow toolkit for downloading, organizing, and automating video and playlist tasks through CLI, Python library, and MCP interfaces.
It is designed for reliable local workflows: single downloads, playlists, batch inputs, page discovery, idempotent execution, structured status tracking, and AI-assisted automation.
VDL uses proven media extraction tools under the hood while adding workflow-level orchestration, state, automation, and developer-facing interfaces.
Highlights
- Local-first: downloads, state, logs, and configuration stay on your machine.
- Multiple interfaces: interactive CLI, headless CLI, Python API, and MCP server.
- Playlist workflows: resolve, inspect, select, filter, and download playlist items.
- Intent-aware interactive mode: detect single videos, native playlists, playlist-member URLs, or crawlable pages before downloading.
- Batch inputs: handle URLs, playlists, link files, and page-discovered media.
- Idempotent execution: avoid accidental duplicate downloads and track status locally.
- Automation-ready: expose media tasks to scripts, apps, and MCP-compatible AI tools.
Installation
Standalone CLI
curl -fsSL https://bildcraft.gitlab.io/products/video-downloader/vdl/install.sh | sh
vdl doctor
Python package
pip install vdl
For MCP support:
pip install "vdl[mcp]"
With uv:
uv tool install vdl
uv tool install "vdl[mcp]"
Quick Start
Download a video:
vdl download <url>
Download a playlist:
vdl playlist <playlist-url>
Resolve a playlist without downloading:
vdl resolve <playlist-url>
Start the interactive CLI:
vdl
Check environment setup:
vdl doctor
Show recent download status:
vdl status
Interfaces
VDL exposes the same workflow capabilities through several interfaces.
flowchart TD
Core["VDL Core / Library"]
Interactive["Interactive CLI"]
Headless["Headless CLI"]
Python["Python Library API"]
MCP["MCP Server"]
Interactive --> Core
Headless --> Core
Python --> Core
MCP --> Core
Core --> Resolve["Resolve URLs, pages, and playlists"]
Core --> Select["Selection and playlist policies"]
Core --> Download["Download execution"]
Core --> State["Local state, logs, and reconciliation"]
| Interface | Use case |
|---|---|
| Interactive CLI | Guided local workflows with prompts and sensible defaults. |
| Headless CLI | Scriptable commands for automation and repeatable jobs. |
| Python API | Embed VDL workflows into apps, services, or notebooks. |
| MCP server | Let MCP-compatible AI clients call local media workflow tools. |
Workflow Model
flowchart LR
Input["URLs / playlists / pages / files"] --> Resolve["Resolve intent"]
Resolve --> Select["Select video(s)"]
Select --> Download["Download"]
Download --> Organize["Organize locally"]
Organize --> Automate["CLI / Library / MCP automation"]
VDL turns mixed inputs into explicit download plans, applies selection and deduplication policies, executes downloads, and records state for later inspection. In interactive mode it can use yt-dlp metadata to distinguish native playlists from single videos, and it can crawl listing pages using site-specific rules from websites.yaml when the user chooses discovery.
Python API
import asyncio
from vdl import VDLClient
async def main() -> None:
client = VDLClient()
result = await client.download(
"https://example.com/video",
quality="1080p",
)
print(result.status)
print(result.output_path)
asyncio.run(main())
Playlist resolution:
import asyncio
from vdl import VDLClient
async def main() -> None:
client = VDLClient()
playlist = await client.resolve_playlist("https://example.com/playlist")
for item in playlist.items:
print(item.title, item.url)
asyncio.run(main())
Tool API
Use vdl.tools when another Python project, agent runtime, or
function-calling LLM needs a stable tool-shaped surface without MCP. These
functions accept plain inputs and return JSON-serializable outputs.
Async example:
import asyncio
from vdl.tools import plan_download
async def main() -> None:
plan = await plan_download("https://example.com/video", quality="best")
print(plan["source_kind"])
print(plan["items"][0]["url"])
asyncio.run(main())
Sync example:
from vdl.tools import doctor_sync
report = doctor_sync()
print(report["version"])
print(report["ffmpeg"])
MCP is not required for this interface. Install vdl normally and import from
vdl.tools.
MCP Server
Install the MCP extra:
pip install "vdl[mcp]"
Add the server to an MCP-compatible client:
{
"mcpServers": {
"vdl": {
"command": "vdl-mcp",
"args": []
}
}
}
The MCP server exposes local media workflow tools such as:
downloaddownload_playlistresolve_playlistget_statusdoctor
The standalone binary release includes both vdl and vdl-mcp. Python package users should install vdl[mcp] when MCP support is needed.
Configuration
VDL stores configuration at:
~/.config/vdl/config.toml
Default local paths follow the XDG base directory layout:
| Purpose | Default path |
|---|---|
| Config | ~/.config/vdl/config.toml |
| State database | ~/.local/state/vdl/vdl.db |
| Logs | ~/.local/state/vdl/logs/ |
| Cache | ~/.cache/vdl/ |
| Downloads | ~/Downloads/VDL/ |
Example configuration:
output_dir = "~/Downloads/VDL"
default_quality = "best"
max_concurrent_downloads = 3
embed_metadata = true
embed_thumbnail = true
expand_nested_playlists = false
Commands
| Command | Description |
|---|---|
vdl |
Start interactive mode. |
vdl download <url> |
Download a video, playlist, or link input. |
vdl playlist <url> |
Download playlist items. |
vdl resolve <url> |
Resolve playlist/page items without downloading. |
vdl status |
Show recent download status. |
vdl doctor |
Check local dependencies and configuration. |
vdl config |
View or update configuration. |
Architecture
VDL keeps business logic in the shared core/library layer. Interfaces are adapters over that core:
VDL Core / Library
├── Interactive CLI
├── Headless CLI
├── Python API
└── MCP server
This keeps the CLI, MCP server, and library interface independent while sharing the same resolution, download, state, and configuration logic.
Key modules:
| Module | Responsibility |
|---|---|
client.py |
Programmatic entry point and workflow orchestration. |
cli.py |
CLI command parsing and headless command execution. |
interactive.py |
Guided interactive mode. |
mcp_server.py |
MCP server adapter. |
acquisition.py |
Convert inputs into download plans. |
playlist.py |
Playlist resolution, inspection, and item selection. |
downloader.py |
Download execution. |
state.py |
SQLite-backed local state. |
config.py |
Configuration loading and persistence. |
crawler.py / site_rules.py |
Page discovery and websites.yaml-driven crawl filtering. |
System Requirements
VDL expects the following tools for full media processing support:
- Python 3.13+
ffmpegffprobe
VDL uses yt-dlp under the hood for media extraction and download support.
Development
uv sync --frozen --all-extras --dev
PYTHONPATH=src uv run pytest -q
uv build --no-sources
Useful tasks:
task --list
task test
task build:pypi
task release
task release:validate
Release
Release automation is handled through GitLab CI.
- PyPI package: built and published in CI through Trusted Publishing.
- macOS arm64 binary: built locally with
scripts/build-macos.shand uploaded to a GitLab Release. - Linux/Windows binaries: optional CI builds behind release variables.
- Installer manifest: published by the manual
vdl-publish-pagesjob after release assets exist.
For the current happy path, run task release on an Apple Silicon Mac from a
clean branch. It wraps the local macOS build, pushes the release tag,
uploads the macOS artifact to GitLab Release, waits for the tag pipeline, and
triggers the manual Pages publish job. The release script auto-loads .env
when present, so GITLAB_TOKEN there is enough for glab API calls.
See docs/RELEASE_PLAYBOOK.md for the full release process.
Related Projects
- yt-dlp — media extraction and download support
- FFmpeg — audio/video processing
- Model Context Protocol — AI tool integration standard
License
MIT License.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vdl-1.0.1.tar.gz.
File metadata
- Download URL: vdl-1.0.1.tar.gz
- Upload date:
- Size: 259.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cdfe4e68393e20bdea2522005c7b8973768939fdc73b9a98674df7733217af7e
|
|
| MD5 |
eb4cb5dfb8d867ce550a60b5d2f51342
|
|
| BLAKE2b-256 |
d14781ba75b8874b2bf3ed51bf77fd3ef59d2f637dd8e897325588e6c8efe35b
|
File details
Details for the file vdl-1.0.1-py3-none-any.whl.
File metadata
- Download URL: vdl-1.0.1-py3-none-any.whl
- Upload date:
- Size: 81.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70f2230f9ee56d52c108881a852bc67ee75a883331046664487b72981b0fd739
|
|
| MD5 |
b4ff716371a5d46be9ad9d0e3099e4ea
|
|
| BLAKE2b-256 |
8f2f4d6dd09c0a17c8aef67a758c8d582b6560993c79d6210442a22e227b356c
|