A local-first media workflow toolkit for downloads, playlists, and automation through CLI and MCP interfaces
Project description
VDL
VDL is a local-first media workflow runtime for building apps and workflows around video downloads, playlists, page discovery, link lists, progress events, and structured results.
It is designed around three surfaces:
| Surface | Default State | Best For |
|---|---|---|
| Python library | Stateless | Embedding media download/discovery in apps and tools |
| Headless CLI | Stateless | Scripts, one-shot jobs, and automation |
| Interactive app | Stateful | Guided terminal UX with history, status, SQLite, and logs |
External products own their own persistence and product workflows. VDL provides the media execution capability; it does not try to become a media library app.
flowchart TD
StandaloneBinary["curl install.sh<br/>Standalone binary"]
PythonPkg["pip install vdl<br/>Python package"]
StandaloneBinary --> CLI["vdl CLI"]
StandaloneBinary --> App["Interactive app"]
StandaloneBinary --> MCP1["vdl-mcp"]
PythonPkg --> Library["Python library<br/>import vdl"]
PythonPkg --> CLI
PythonPkg --> App
PythonPkg --> Tools["vdl.tools"]
PythonPkg --> MCP2["MCP server<br/>pip install vdl[mcp]"]
style StandaloneBinary fill:#0277bd,color:#fff
style PythonPkg fill:#7b1fa2,color:#fff
style MCP2 fill:#e65100,color:#fff
Install vdl Binary
Use the standalone installer when you want the commands without setting up a Python project:
curl -fsSL https://bildcraft.gitlab.io/products/video-downloader/vdl/install.sh | sh
This gives you:
vdlvdl-mcp
Interactive Download App
Start the interactive app with:
vdl
It opens an interactive terminal interface:
The app guides you through:
- Input — paste a URL, local file path, or
.txtlink list - Quality — choose preferred resolution (best, 2160p, 1440p, 1080p, 720p, 480p, or audio)
- Output — set download directory (default:
~/Downloads/VDL) - Options — save thumbnails and subtitles
- Mode — download directly or crawl the page for more links
Perfect for downloading single videos, playlists, or discovering downloadable media on websites.
The interactive app is intentionally stateful: it creates local app directories, SQLite state, and logs so it can provide history/status/session UX.
CLI Commands
Check the installed binary:
vdl --version
Run direct commands:
vdl download https://example.com/video
vdl crawl https://example.com/gallery --first 3
vdl crawl https://example.com/gallery --crawl-pagination --first 3
vdl playlist https://example.com/playlist
vdl resolve https://example.com/playlist
vdl status
vdl doctor
These headless commands are stateless by default. They do not implicitly create the interactive SQLite database or app logs.
Crawl-specific behavior is intentionally different from direct download and playlist flows:
vdl crawl ... --first 3means first 3 successful downloads, not first 3 discovered links- crawl applies its own admission policy, including a default 7 MB minimum file size
--allow-tinydisables that crawl-only minimum--crawl-paginationfollows pagination/navigation pages discovered during crawl- direct downloads and playlists are not guarded by the crawl minimum, which keeps audio and other intentional small downloads working normally
When a site needs authentication, the headless commands can take cookies too:
vdl download --cookies cookies.txt https://example.com/video
vdl crawl --cookies-from-browser firefox https://example.com/gallery --first 3
Install as Python Library
Use the Python package when you want to embed VDL in another Python project:
pip install vdl
That gives you:
- the
vdlPython library - the
vdlCLI - the interactive app entrypoint
vdl.tools
Use the library directly:
import asyncio
from vdl import CrawlerPolicy, VDLClient
async def main() -> None:
client = VDLClient()
result = await client.download("https://example.com/video")
print(result.status)
crawl_plan = await client.discover(
"https://example.com/gallery",
crawler_policy=CrawlerPolicy(crawl_pagination_pages=True),
)
crawl_results = await client.execute_plan(
crawl_plan,
crawler_policy=CrawlerPolicy(first_successful=3),
)
print(len(crawl_results))
asyncio.run(main())
The library is stateless by default and embeddable. Your application owns any database, history, user library, retries, or product-specific workflow state.
Use the tool surface:
import asyncio
from vdl.tools import download_video
async def main() -> None:
result = await download_video("https://example.com/video")
print(result["status"])
asyncio.run(main())
MCP Support
Install the MCP extra when you want vdl-mcp:
pip install "vdl[mcp]"
That adds MCP support on top of the Python package. If you installed the
standalone binary, vdl-mcp is already included there too.
Example MCP client config:
{
"mcpServers": {
"vdl": {
"command": "vdl-mcp",
"args": []
}
}
}
Examples
Each demo in demos/ illustrates a single workflow:
| Demo | Use Case |
|---|---|
download_video.py |
Download a single video |
download_playlist.py |
Download an entire playlist |
resolve_playlist.py |
Inspect playlist items before downloading |
download_link_file.py |
Batch download URLs from a .txt file |
crawl_websites.py |
Discover and download media on a webpage |
All demos use placeholder URLs — copy and edit with your own.
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.5.tar.gz.
File metadata
- Download URL: vdl-1.0.5.tar.gz
- Upload date:
- Size: 1.4 MB
- 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 |
81375149a4c467b870e27a09c3a644f487068f34832f28ef44f32ae668a11188
|
|
| MD5 |
55a9eeba863889ceb98fed2014800dde
|
|
| BLAKE2b-256 |
c5f431160b4a63cc2d772f4dd0cb788097c89db5c54120b88b38b4018be765e4
|
File details
Details for the file vdl-1.0.5-py3-none-any.whl.
File metadata
- Download URL: vdl-1.0.5-py3-none-any.whl
- Upload date:
- Size: 108.0 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 |
f3695c394680b44acbcaf498578e7999591694d3904c3db69869822b98308b3d
|
|
| MD5 |
f610e319ed734d87e8eaf37c42f648df
|
|
| BLAKE2b-256 |
bf4ff5a663c244fcf3c0849e1a51cdc11334fd2e7e4bd6be9947cf2efdaa73f5
|