Python API¶
VDL exposes a clean async API via VDLClient and helper functions in vdl.tools.
This developer documentation is published live at: https://video-downloader-657d90.gitlab.io/python-api/
VDLClient¶
The main entry point for programmatic access.
Construction¶
from vdl import VDLClient
# Stateless by default; loads config from ~/.config/vdl/config.toml
client = VDLClient()
# With custom config
from vdl import VDLConfig
from pathlib import Path
config = VDLConfig(
output_dir=Path("~/Videos"),
default_quality="1080p",
max_concurrent_downloads=2
)
client = VDLClient(config=config)
# Optional crawl defaults for discovery plans only
from vdl import CrawlerPolicy
client = VDLClient(
config=config,
crawler_policy=CrawlerPolicy(first_successful=3),
)
Core Methods¶
| Method | Returns | Description |
|---|---|---|
download(url, ...) |
DownloadResult |
Download a single URL |
download_playlist(url, ...) |
list[DownloadResult] |
Download all playlist items |
resolve_playlist(url) |
PlaylistInfo |
Inspect playlist without downloading |
plan(input, ...) |
AcquisitionPlan |
Classify input and build a plan |
plan_with_probe(input, ...) |
AcquisitionPlan |
Plan with network inspection |
discover(url, ..., on_status, crawler_policy) |
AcquisitionPlan |
Crawl a page for media |
execute_plan(plan, ...) |
list[DownloadResult] |
Execute a plan |
execute_batch(plan, ...) |
BatchSummary |
Execute and get summary stats |
status(id=None) |
StatusRecord or list or None |
Query download history |
doctor() |
DoctorReport |
Health check |
Common Parameters¶
Many methods accept these optional parameters:
result = await client.download(
url="https://example.com/video",
output_dir="~/Videos", # Override config
quality=Quality.P720, # From vdl
if_exists=IfExists.RENAME, # From vdl
on_progress=callback, # DownloadProgress → None (per-call)
cancel_token=token, # CancellationToken
write_thumbnail=True, # Write .jpg sidecar
write_subtitles=True, # Write subtitle files
subtitle_langs="en,fr" # Comma-separated codes
)
Progress Event (client-wide)¶
client.progress is an Event[DownloadProgress] that fires for every progress
update across all downloads. Subscribe once and receive progress from all
download(), download_playlist(), and execute_plan() calls:
async def my_handler(p: DownloadProgress) -> None:
print(f"[{p.status.value}] {p.title}: {p.percent}%")
client.progress += my_handler # subscribe
# ... run downloads ...
client.progress -= my_handler # unsubscribe when done
on_progress= on individual calls still works and takes priority over the
client-level event for that specific call.
Models and Enums¶
All exported via vdl or vdl.models:
Enums¶
from vdl.models import Quality, IfExists, DownloadStatus, SkipReason
# Quality options
Quality.BEST, Quality.P1080, Quality.P720, Quality.AUDIO, ...
# Collision policy
IfExists.RENAME, IfExists.SKIP, IfExists.OVERWRITE, IfExists.FAIL
# Download status
DownloadStatus.COMPLETED, SKIPPED, FAILED, CANCELLED, ...
# Skip reason
SkipReason.ALREADY_PRESENT # Only value
Dataclasses¶
from vdl import (
DownloadResult, DownloadMetadata, DownloadSidecars,
PlaylistInfo, PlaylistItem,
DownloadRequest, AcquisitionPlan, AcquisitionItem,
BatchSummary, CrawlerDecision, CrawlerPolicy,
DoctorReport, ReconciliationReport,
DownloadProgress,
)
DownloadResult.metadata is a typed DownloadMetadata instance:
result = await client.download("URL")
# Typed access — no key lookups needed
print(result.metadata.title)
print(result.metadata.channel)
print(result.metadata.duration_sec)
print(result.metadata.playlist_title)
print(result.metadata.playlist_count)
# Sidecar files written alongside the video
print(result.metadata.sidecars.thumbnail) # Path | None
print(result.metadata.sidecars.subtitles) # dict[str, Path] — lang → file
CrawlerPolicy applies only to discovery plans. Use it to control crawl admission and success-target execution without affecting direct downloads:
from vdl import CrawlerPolicy, VDLClient
client = VDLClient(crawler_policy=CrawlerPolicy(first_successful=2))
plan = await client.discover(
"https://example.com/gallery",
crawler_policy=CrawlerPolicy(min_filesize_bytes=10 * 1024 * 1024),
)
results = await client.execute_plan(
plan,
crawler_policy=CrawlerPolicy(first_successful=3),
)
expanded = await client.discover(
"https://example.com/gallery",
crawler_policy=CrawlerPolicy(crawl_pagination_pages=True),
)
Exception Hierarchy¶
from vdl import VDLError
# All exceptions inherit from VDLError
VDLError # Base
├── VDLConfigError # Config load/save/validation
├── VDLInputError # User input parsing
├── VDLDownloadError # Download operation
├── VDLPlaylistError # Playlist resolution
├── VDLStateError # Persistent state
├── VDLDependencyError # Missing ffmpeg, yt-dlp, etc.
├── VDLSelectionError # Playlist selection
├── VDLCrawlerError # Page crawling
└── VDLCancelled # (Internal; don't catch)
Catch as needed:
try:
result = await client.download("URL")
except VDLConfigError:
print("Bad config")
except VDLDownloadError as e:
print(f"Download failed: {e}")
Logger Interface¶
Inject a custom logger to capture events:
from vdl import Logger
class MyLogger(Logger):
def log(self, level: str, event: str, **kwargs) -> None:
print(f"[{level}] {event}: {kwargs}")
client = VDLClient(logger=MyLogger())
Or use the built-in NullLogger (default, no output).
vdl.tools — Transport-Agnostic Functions¶
Thin async functions that delegate to VDLClient. Useful for MCP, RPC, or other integrations:
from vdl.tools import download_video, resolve_playlist, get_status, doctor
# Each has an async variant and a sync variant
result = await download_video(
url="...",
quality="720p",
output_dir="~/Videos",
if_exists="rename",
client=client
)
# Sync variant (calls asyncio.run internally)
result = download_video_sync(
url="...",
quality="720p",
output_dir="~/Videos",
client=client
)
Available tools:
- download_video() / download_video_sync()
- plan_download() / plan_download_sync()
- resolve_playlist() / resolve_playlist_sync()
- get_status() / get_status_sync()
- doctor() / doctor_sync()
Import Guide¶
# Main client
from vdl import VDLClient
# Config
from vdl import VDLConfig
# Models and enums (all exported via __init__)
from vdl import (
DownloadResult, DownloadMetadata, DownloadSidecars,
DownloadStatus, SkipReason, Quality, IfExists,
PlaylistInfo, PlaylistItem,
DownloadProgress,
AcquisitionPlan, AcquisitionItem, PlanKind,
BatchSummary, DoctorReport,
ReconciliationEntry, ReconciliationReport,
)
# Exceptions
from vdl import VDLError, VDLDownloadError, VDLConfigError
# Utilities
from vdl.cancellation import CancellationToken, CancellationController
from vdl import DownloadProgress
# Tools (stateless functions)
from vdl.tools import download_video, resolve_playlist, doctor
# Logger
from vdl import Logger, NullLogger
Complete Example¶
import asyncio
from vdl import VDLClient, VDLConfig, IfExists, Quality, DownloadProgress, DownloadStatus
from vdl.cancellation import CancellationToken
from pathlib import Path
async def main():
client = VDLClient()
token = CancellationToken()
# Client-wide progress: subscribe once, receives events from all downloads
async def on_progress(p: DownloadProgress) -> None:
if p.percent is not None:
print(f" [{p.status.value}] {p.title}: {p.percent}%")
client.progress += on_progress
# Single video
result = await client.download(
"https://example.com/video",
quality=Quality.P720,
if_exists=IfExists.SKIP,
write_thumbnail=True,
cancel_token=token,
)
if result.status == DownloadStatus.COMPLETED:
print(f"Done: {result.output_path}")
print(f"Title: {result.metadata.title}")
print(f"Channel: {result.metadata.channel}")
if result.metadata.sidecars.thumbnail:
print(f"Thumbnail: {result.metadata.sidecars.thumbnail}")
elif result.status == DownloadStatus.SKIPPED:
print(f"Already present: {result.output_path}")
else:
print(f"Error: {result.error}")
# Playlist — first 3 items only
results = await client.download_playlist(
"https://example.com/playlist",
first_n=3,
quality=Quality.P1080,
write_subtitles=True,
subtitle_langs="en",
)
for r in results:
status = "✓" if r.status == DownloadStatus.COMPLETED else "✗"
print(f"{status} {r.metadata.title} — {r.metadata.playlist_index}/{r.metadata.playlist_count}")
client.progress -= on_progress
asyncio.run(main())
Working Examples¶
See the demos/ directory:
- download_video.py — single video
- download_playlist.py — full playlist
- resolve_playlist.py — read-only inspection
- download_link_file.py — batch from file
- crawl_websites.py — discovery workflow
See Also¶
- API Reference — full signatures and fields
- Cancellation — token and controller details
- Configuration — config options