ADR 0005: yt-dlp as the default metadata source¶
Status¶
Accepted — 2026-09-19
Context¶
thumbforge fetch <url> must resolve a video, playlist or channel URL into titles, descriptions, durations, publish dates, playlist ordering and source thumbnail URLs, without asking the user for a Google Cloud project or API key. Tests must run offline and deterministically.
Decision¶
-
sources/base.pydefines aMetadataSourceProtocol:class MetadataSource(Protocol): key: ClassVar[str] # "ytdlp", "api" async def resolve(self, url: str) -> ResolvedUrl # kind: video|playlist|channel + youtube_id async def fetch_video(self, youtube_id: str) -> VideoMeta async def fetch_playlist(self, youtube_id: str) -> PlaylistMeta # includes ordered items async def fetch_channel(self, youtube_id: str) -> ChannelMeta -
sources/ytdlp.py(YtDlpSource) is the default (--source ytdlp). It importsyt_dlpas a library (not a subprocess) withquiet=True, skip_download=True, extract_flat="in_playlist"for playlists and a full extraction for single videos. Theyt_dlplogger is routed through structlog (ADR 0015). - Playlist entries map to
playlist_itemrows:positionfromplaylist_index,part_number = position + 1by default; the exact field set present underextract_flatis confirmed by spike S11 and captured in a fixture. yt-dlpis pinned inuv.lock; a bump is a deliberate PR that re-records fixtures.- Fixtures:
tests/fixtures/ytdlp/*.jsonare produced by apytest -m recordrecorder that runs live once and stores the raw info-dict; unit tests replay them withyt_dlp.YoutubeDL.extract_infomonkeypatched. Live tests are-m integrationand opt-in. - Network errors retry via
tenacitywith the same backoff as providers (PLAN.md§7.2); everything else raisesSourceError(exit1). - The YouTube Data API v3 source (
sources/youtube_api.py, extraapi) is Phase 8 and depends on decision D7.
Consequences¶
- No credentials for the common path;
fetchworks for public content immediately. - YouTube page changes can break
yt-dlp; the pin plus recorded fixtures keep the test suite green and make breakage visible only in the opt-in live tests. - The Data API source, when added, must satisfy the same Protocol and the same fixture-replay test pattern.
Alternatives considered¶
- YouTube Data API v3 only — rejected: requires an API key and a Cloud project before the first command works, and quota (10 000 units/day) is easily exhausted while iterating.
pytube— rejected: slower maintenance cadence than yt-dlp and frequent breakage on playlist extraction.- Scraping
youtube.comHTML directly — rejected: duplicates what yt-dlp already maintains.