API Reference¶
Complete reference for all public exports and their signatures.
VDLClient¶
Main facade for all operations.
class VDLClient:
# Event fired for every progress update across all downloads on this client.
# Subscribe with client.progress += callback; unsubscribe with client.progress -= callback.
progress: Event[DownloadProgress]
async def download(
url: str,
output_dir: str | Path | None = None,
quality: Quality | str = Quality.BEST,
on_progress: ProgressCallback | None = None,
if_exists: IfExists | str = IfExists.RENAME,
cancel_token: CancellationToken | None = None,
write_thumbnail: bool | None = None,
write_subtitles: bool | None = None,
subtitle_langs: str | None = None,
cookies_file: str | Path | None = None,
cookies_from_browser: str | None = None,
) -> DownloadResult: ...
async def resolve_playlist(url: str) -> PlaylistInfo: ...
async def download_playlist(
url: str,
output_dir: str | Path | None = None,
on_progress: ProgressCallback | None = None,
if_exists: IfExists | str = IfExists.SKIP,
cancel_token: CancellationToken | None = None,
quality: Quality | str = Quality.BEST,
write_thumbnail: bool | None = None,
write_subtitles: bool | None = None,
subtitle_langs: str | None = None,
first_n: int | None = None,
items: list[int] | None = None,
most_recent_n: int | None = None,
) -> list[DownloadResult]: ...
# Advanced: explicit PlaylistSelection object; also exposes controller
async def download_playlist_with_selection(
url: str,
selection: PlaylistSelection | list[int] | None = None,
output_dir: str | Path | None = None,
on_progress: ProgressCallback | None = None,
if_exists: IfExists | str = IfExists.SKIP,
cancel_token: CancellationToken | None = None,
quality: Quality | str = Quality.BEST,
write_thumbnail: bool | None = None,
write_subtitles: bool | None = None,
subtitle_langs: str | None = None,
controller: CancellationController | None = None,
) -> list[DownloadResult]: ...
async def plan(
raw_input: str,
output_dir: str | Path | None = None,
quality: Quality | str = Quality.BEST,
if_exists: IfExists | str = IfExists.RENAME,
) -> AcquisitionPlan: ...
async def plan_with_probe(
raw_input: str,
output_dir: str | Path | None = None,
quality: Quality | str = Quality.BEST,
if_exists: IfExists | str = IfExists.RENAME,
) -> AcquisitionPlan: ...
async def discover(
url: str,
output_dir: str | Path | None = None,
quality: Quality | str = Quality.BEST,
if_exists: IfExists | str = IfExists.RENAME,
on_status: DiscoveryStatusCallback | None = None,
crawler_policy: CrawlerPolicy | None = None,
) -> AcquisitionPlan: ...
async def discover_from_navigation(
source_url: str,
page_urls: list[str],
*,
output_dir: Path | None = None,
quality: Quality = Quality.BEST,
if_exists: IfExists = IfExists.RENAME,
source_title: str | None = None,
crawler_policy: CrawlerPolicy | None = None,
) -> AcquisitionPlan: ...
def select_plan_items(plan: AcquisitionPlan, selection: list[int]) -> AcquisitionPlan: ...
def plan_requires_choice(plan: AcquisitionPlan) -> Literal["none", "ambiguous_playlist", "playlist_selection", "discovered_selection"]: ...
async def execute_plan(
plan: AcquisitionPlan,
*,
selection: PlaylistSelection | list[int] | None = None,
prefer_playlist: bool = False,
execution_policy: ExecutionPolicy | None = None,
crawler_policy: CrawlerPolicy | None = None,
on_progress: ProgressCallback | None = None,
cancel_token: CancellationToken | None = None,
write_thumbnail: bool | None = None,
write_subtitles: bool | None = None,
subtitle_langs: str | None = None,
controller: CancellationController | None = None,
) -> list[DownloadResult]: ...
async def execute_batch(
plan: AcquisitionPlan,
selection: list[int] | None = None,
prefer_playlist: bool = False,
on_progress: ProgressCallback | None = None,
cancel_token: CancellationToken | None = None,
write_thumbnail: bool | None = None,
write_subtitles: bool | None = None,
subtitle_langs: str | None = None,
) -> BatchSummary: ...
async def status(id: str | None = None) -> StatusRecord | list[StatusRecord] | None: ...
async def reconcile_status(id: str | None = None, limit: int = 100) -> ReconciliationReport: ...
async def doctor() -> DoctorReport: ...
VDLConfig¶
Configuration dataclass (frozen).
@dataclass(frozen=True)
class VDLConfig:
output_dir: Path
default_quality: str = "best"
audio_only: bool = False
max_concurrent_downloads: int = 3
embed_metadata: bool = True
embed_thumbnail: bool = True
write_thumbnail: bool = False
write_subtitles: bool = False
subtitle_langs: str = "en"
max_playlist_items: int | None = None
expand_nested_playlists: bool = False
concurrent_fragments: int | None = None
write_info_json: bool = False
extractor_args: dict[str, Any] | None = None
ytdlp_channel: str | None = None
js_runtime_path: str | None = None
site_rules_path: Path | None = None
Enums¶
DownloadStatus¶
class DownloadStatus(str, Enum):
QUEUED = "queued"
STARTING = "starting"
DOWNLOADING = "downloading"
PROCESSING = "processing"
COMPLETED = "completed"
SKIPPED = "skipped"
FAILED = "failed"
CANCELLED = "cancelled"
IfExists¶
class IfExists(str, Enum):
FAIL = "fail"
SKIP = "skip"
OVERWRITE = "overwrite"
RENAME = "rename"
SkipReason¶
class SkipReason(str, Enum):
ALREADY_PRESENT = "already_present"
Note: ALREADY_COMPLETED was removed. Only ALREADY_PRESENT is valid.
Quality¶
class Quality(str, Enum):
BEST = "best"
MEDIUM = "medium"
LOW = "low"
P2160 = "2160p"
P1440 = "1440p"
P1080 = "1080p"
P720 = "720p"
P480 = "480p"
AUDIO = "audio"
Available from both vdl and vdl.models:
from vdl import Quality # preferred
from vdl.models import Quality # also fine
Dataclasses¶
DownloadResult¶
@dataclass(frozen=True)
class DownloadResult:
id: str
source_url: str
status: DownloadStatus
quality: Quality | None
request_key: str | None
output_path: Path | None
error: str | None
skipped_reason: str | None
skip_reason: SkipReason | None
metadata: DownloadMetadata # typed — see DownloadMetadata below
error_category: str | None
DownloadMetadata¶
Typed media metadata extracted from a download. Replaces the old untyped dict[str, Any].
@dataclass(frozen=True)
class DownloadMetadata:
# Provider identity
provider: str | None # e.g. "youtube"
provider_key: str | None
provider_item_id: str | None
provider_kind: str | None
# URLs
original_url: str | None
canonical_url: str | None
webpage_url: str | None
# Basic content
title: str | None
fulltitle: str | None
description: str | None
duration_sec: float | None
# Channel / creator
channel: str | None
channel_id: str | None
channel_url: str | None
uploader: str | None
uploader_id: str | None
# Timestamps
upload_date: str | None # YYYYMMDD string
published_timestamp: int | float | None
release_timestamp: int | float | None
language: str | None
# Taxonomy
tags: list[str]
categories: list[str]
# Thumbnail
thumbnail_url: str | None
thumbnails: list[dict[str, Any]]
# Technical
width: int | None
height: int | None
video_codec: str | None
audio_codec: str | None
# Playlist / collection
playlist_id: str | None
playlist_title: str | None
playlist_index: int | None # 1-based position
playlist_count: int | None
playlist_uploader: str | None
playlist_uploader_id: str | None
# Local output
downloaded_path: str | None # absolute path to downloaded file
sidecars: DownloadSidecars
raw_subset: dict[str, Any] # small yt-dlp debug subset
DownloadSidecars¶
Paths to files written alongside the downloaded media.
@dataclass(frozen=True)
class DownloadSidecars:
thumbnail: Path | None # thumbnail sidecar, if written
subtitles: dict[str, Path] # lang code → subtitle file path
PlaylistInfo¶
@dataclass(frozen=True)
class PlaylistInfo:
url: str
title: str
items: list[PlaylistItem]
metadata: dict[str, Any]
PlaylistItem¶
@dataclass(frozen=True)
class PlaylistItem:
url: str
id: str
title: str
index: int
duration_seconds: int | None
selected: bool
metadata: dict[str, Any]
DownloadProgress¶
@dataclass(frozen=True)
class DownloadProgress:
id: str
status: DownloadStatus
percent: int | None
speed_bps: float
title: str
eta: int | None
current_file: str | None
output_path: str | None
error: str | None
updated_at: datetime
AcquisitionPlan¶
@dataclass(frozen=True)
class AcquisitionPlan:
items: list[AcquisitionItem]
source_kind: InputKind
metadata: dict[str, Any]
@property
def total(self) -> int: ...
@property
def kind(self) -> PlanKind: ...
AcquisitionItem¶
@dataclass(frozen=True)
class AcquisitionItem:
source_url: str
output_dir: Path
quality: Quality
kind: DownloadKind
if_exists: IfExists
subdirectory: str | None
is_playlist: bool
metadata: dict[str, Any]
PlaylistSelection¶
Controls which items to download from a playlist.
@dataclass(frozen=True)
class PlaylistSelection:
mode: Literal["all", "first_n", "indexes", "most_recent_n"]
first_n: int | None = None # used when mode="first_n"
indexes: frozenset[int] | None = None # 0-based; used when mode="indexes"
most_recent_n: int | None = None # used when mode="most_recent_n"
Convenience parameters on download_playlist() build these automatically:
| Parameter | Mode | Example |
|---|---|---|
| (none) | "all" |
all items |
first_n=3 |
"first_n" |
first 3 items |
items=[0, 2, 4] |
"indexes" |
items 1, 3, 5 (0-based) |
most_recent_n=5 |
"most_recent_n" |
5 most recently published |
BatchSummary¶
@dataclass(frozen=True)
class BatchSummary:
results: list[DownloadResult]
completed: list[DownloadResult] # items with status COMPLETED
skipped: list[DownloadResult] # items with status SKIPPED
failed: list[DownloadResult] # items with status FAILED
total: int
@classmethod
def from_results(cls, results: list[DownloadResult]) -> BatchSummary: ...
DoctorReport¶
@dataclass(frozen=True)
class DoctorReport:
version: str
ffmpeg_available: bool
ffprobe_available: bool
ytdlp_available: bool
config_ok: bool
state_ok: bool
output_dir_ok: bool
ffmpeg_available: bool
ffprobe_available: bool
mcp_available: bool
config_dir_ok: bool
state_dir_ok: bool
cache_dir_ok: bool
repairs: list[str]
details: dict[str, str]
ReconciliationEntry, ReconciliationReport¶
@dataclass(frozen=True)
class ReconciliationEntry:
id: str
source_url: str
output_path: str | None
status: Literal["exists", "missing", "no_output_path"]
@dataclass(frozen=True)
class ReconciliationReport:
entries: list[ReconciliationEntry]
total: int
missing: int
Exceptions¶
class VDLError(Exception): ...
class VDLConfigError(VDLError): ...
class VDLInputError(VDLError): ...
class VDLDownloadError(VDLError): ...
class VDLPlaylistError(VDLError): ...
class VDLStateError(VDLError): ...
class VDLDependencyError(VDLError): ...
class VDLSelectionError(VDLError): ...
class VDLCrawlerError(VDLError): ...
class VDLCancelled(VDLError): ... # Internal; don't catch
Callbacks¶
ProgressCallback¶
ProgressCallback = Callable[[DownloadProgress], None] | Callable[[DownloadProgress], Awaitable[None]]
DiscoveryStatusCallback¶
DiscoveryStatusCallback = Callable[[str, dict[str, Any]], None]
See Also¶
- Python API — usage guide
- Errors — exception handling patterns