Skip to content

Playlists

VDL can both resolve playlists (list items without downloading) and download all or selected items.

Resolve a Playlist

Inspect a playlist without downloading:

CLI

vdl resolve "https://example.com/playlist"

Output:

  1 | First Video      | https://example.com/item1 | 3:45
  2 | Second Video     | https://example.com/item2 | 5:20
  3 | Third Video      | https://example.com/item3 | 2:10
  ...

Get structured JSON output:

vdl resolve "https://example.com/playlist" --json

Python

import asyncio
from vdl import VDLClient

async def main():
    client = VDLClient()
    playlist = await client.resolve_playlist("https://example.com/playlist")

    print(f"Title: {playlist.title}")
    print(f"Items: {len(playlist.items)}")

    for item in playlist.items:
        print(f"  {item.index} | {item.title} | {item.duration_seconds}s")

asyncio.run(main())

Download a Playlist

Download all items from a playlist.

CLI

vdl playlist "https://example.com/playlist"

Or use vdl download with --playlist:

vdl download "https://example.com/playlist" --playlist

Python

import asyncio
from vdl import VDLClient, Quality

async def main():
    client = VDLClient()
    results = await client.download_playlist(
        "https://example.com/playlist",
        quality=Quality.P720,
        write_thumbnail=True,
    )

    for result in results:
        print(f"{result.source_url}{result.status}")
        if result.metadata.title:
            print(f"  Title: {result.metadata.title}")
        if result.metadata.sidecars.thumbnail:
            print(f"  Thumbnail: {result.metadata.sidecars.thumbnail}")
        if result.error:
            print(f"  Error: {result.error}")

asyncio.run(main())

Playlist Selection

Download a subset of items rather than the full playlist.

CLI

# First 5 items
vdl playlist "URL" --first-items 5

# Specific items by 1-based position (comma-separated)
vdl playlist "URL" --items 1,3,5

# Contiguous range (1-based, inclusive)
vdl playlist "URL" --range 1:10

# 3 most recently published items
vdl playlist "URL" --most-recent 3

The same flags work with vdl download --playlist.

Python

# First 5 items
results = await client.download_playlist("URL", first_n=5)

# Specific items by 0-based index
results = await client.download_playlist("URL", items=[0, 2, 4])

# 3 most recently published
results = await client.download_playlist("URL", most_recent_n=3)

At most one selection parameter (first_n, items, most_recent_n) may be specified per call; combining them raises VDLError.

PlaylistInfo and PlaylistItem

PlaylistInfo

@dataclass(frozen=True)
class PlaylistInfo:
    url: str                        # Playlist URL
    title: str                      # Playlist title
    items: list[PlaylistItem]       # All items
    metadata: dict[str, Any]        # Platform metadata

PlaylistItem

@dataclass(frozen=True)
class PlaylistItem:
    url: str                        # Item URL
    id: str                         # Unique item ID
    title: str                      # Item title
    index: int                      # Position in playlist (1-based)
    duration_seconds: int | None    # Duration (if available)
    selected: bool                  # Selected for download (default True)
    metadata: dict[str, Any]        # Platform metadata

Concurrency

Playlist downloads are concurrent. The max_concurrent_downloads config controls parallelism:

vdl config set max_concurrent_downloads 5
vdl playlist "URL"  # Downloads up to 5 items in parallel

This respects bandwidth limits and rate limits of the source.

Collision Handling

When downloading a playlist, --if-exists applies to each item:

# Skip items that already exist
vdl playlist "URL" --if-exists skip

# Rename to avoid collision (default)
vdl playlist "URL" --if-exists rename

# Overwrite existing items
vdl playlist "URL" --if-exists overwrite

Python:

from vdl.models import IfExists

results = await client.download_playlist(
    "URL",
    if_exists=IfExists.SKIP
)

Partial Failure Handling

If one item fails, the download continues with remaining items. Each item has its own status:

results = await client.download_playlist("URL")

completed = [r for r in results if r.status == DownloadStatus.COMPLETED]
failed = [r for r in results if r.status == DownloadStatus.FAILED]
skipped = [r for r in results if r.status == DownloadStatus.SKIPPED]

print(f"Completed: {len(completed)}, Failed: {len(failed)}, Skipped: {len(skipped)}")

Limiting Playlist Expansion

By default, VDL downloads all items. Use --first-items N (CLI) or first_n=N (Python) to download only the first N, or use max_playlist_items in config as a global cap:

vdl config set max_playlist_items 10
vdl playlist "URL"  # capped at 10 by config
from vdl import VDLConfig
from pathlib import Path

config = VDLConfig(output_dir=Path("~/Videos"), max_playlist_items=10)
client = VDLClient(config=config)
results = await client.download_playlist("URL")

Quality

All items in the playlist are downloaded at the specified quality:

vdl playlist "URL" --quality 720p
vdl playlist "URL" --quality audio

For thumbnails and subtitles, use interactive mode or the Python API sidecar options.

Status Tracking

Only available in interactive mode. Playlist downloads are recorded in the state database:

vdl status

Shows all recent downloads, including all items from playlists.

Workflow Diagram

sequenceDiagram
    User->>VDLClient: download_playlist(url)
    VDLClient->>PlaylistService: resolve(url)
    PlaylistService->>YtDlpPlaylistResolver: resolve(url)
    YtDlpPlaylistResolver-->>PlaylistService: PlaylistInfo

    PlaylistService->>PlaylistService: create_requests(items)

    loop Each item (up to max_concurrent_downloads)
        PlaylistService->>DownloadExecutionService: download(item.url)
        DownloadExecutionService->>YtDlpDownloader: download()
        YtDlpDownloader-->>DownloadExecutionService: DownloadResult
        DownloadExecutionService-->>PlaylistService: result
    end

    PlaylistService-->>User: list[DownloadResult]

Examples

See demos/download_playlist.py and demos/resolve_playlist.py.