Skip to content

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