Spike results — yt-dlp¶
S11 — flat playlist fields under extract_flat (closed 2026-09-20)¶
yt-dlp version: resolved by uv run --with yt-dlp on 2026-09-20. Playlist used:
https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI ("Popular Music
Videos", 183 items) — a large, stable, public playlist.
Command¶
uv run --with yt-dlp python -c "
import yt_dlp
url = 'https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI'
with yt_dlp.YoutubeDL({'extract_flat':'in_playlist','quiet':True,'skip_download':True}) as ydl:
info = ydl.extract_info(url, download=False)
print(sorted(info['entries'][0]))
"
Result — playlist level¶
TOP-LEVEL KEYS: ['_type', 'availability', 'channel', 'channel_follower_count', 'channel_id',
'channel_url', 'description', 'epoch', 'extractor', 'extractor_key', 'id', 'modified_date',
'original_url', 'playlist_count', 'release_year', 'tags', 'thumbnails', 'title', 'uploader',
'uploader_id', 'uploader_url', 'view_count', 'webpage_url', 'webpage_url_basename',
'webpage_url_domain']
playlist title : Popular Music Videos
playlist id : PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI
playlist_count : 183
channel/uploader : Music / Music
channel_id : UC-9-kyTW8ZkZNDHQJ6FgpwQ
n entries : 183
playlist_count matches len(entries), and the playlist carries its own title,
description, channel_id and thumbnails — enough to fill PlaylistMeta and the owning
ChannelMeta from a single call.
Result — entry level¶
ENTRY[0] KEYS: ['__x_forwarded_for_ip', '_type', 'availability', 'channel', 'channel_id',
'channel_url', 'creators', 'duration', 'id', 'ie_key', 'live_status', 'thumbnails',
'timestamp', 'title', 'uploader', 'uploader_url', 'url', 'view_count']
playlist_index present in any entry: False
timestamp values (first 5) : [None, None, None, None, None]
description present in any entry : False
duration None count : 0 / 183
channel_id None count : 0 / 183
keys missing from some entries : ['uploader_id']
dict.get() returns None for a missing key and for a key whose value is None, so the
two were measured apart explicitly — the distinction decides whether P2.2 can rely on a
.get() default or must branch:
$ uv run --with yt-dlp python -c "... for k in (...): present = sum(1 for e in es if k in e) ..."
description ABSENT from all entries
timestamp present on all, value always None
availability present on all, value always None
playlist_index ABSENT from all entries
duration present on all, 183 non-null
uploader_id present on 27/183, 27 non-null
view_count present on all, 183 non-null
live_status present on all, value always None
| Field | Under extract_flat |
Note |
|---|---|---|
id |
✅ always | 11-char video id |
title |
✅ always | |
duration |
✅ always (183/183) | seconds; differs by ±1s from a full extract |
channel_id |
✅ always (183/183) | the video's channel, not the playlist owner |
channel |
✅ always | |
url |
✅ always | canonical watch?v= form |
thumbnails |
✅ always | list, max 336×188 — no maxresdefault |
view_count |
✅ always | |
playlist_index |
❌ key absent | does not exist on any entry |
description |
❌ key absent | does not exist on any entry |
timestamp |
⚠️ key present, always None |
so no published_at |
availability |
⚠️ key present, always None |
|
live_status |
⚠️ key present, always None |
cannot filter live streams from a flat pass |
uploader_id |
⚠️ present on 27/183 | do not rely on it |
Result — full extract of one video, for comparison¶
$ uv run --with yt-dlp python -c "...YoutubeDL({'quiet':True,'skip_download':True})..."
{
"id": "fOT0BUpITw8",
"title": "BELLAKEO (Video Oficial) - Peso Pluma, Anitta",
"duration": 234,
"channel_id": "UCzrM_068Odho89mTRrrxqbA",
"upload_date": "20231208",
"timestamp": 1701993610,
"description": "BELLAKEO (Video Oficial) - Peso Pluma, Anitta\nDouble P Records\n\n...",
"view_count": 761508498,
"live_status": "not_live",
"availability": "public",
"thumbnail": "https://i.ytimg.com/vi/fOT0BUpITw8/maxresdefault.jpg"
}
A full extract also emits a warning worth knowing about, though it does not affect metadata:
WARNING: [youtube] No supported JavaScript runtime could be found. Only deno is enabled by
default ... YouTube extraction without a JS runtime has been deprecated, and some formats may
be missing.
Formats are irrelevant here — thumbforge never downloads media — but the warning must be
suppressed or allow-listed so it does not pollute fetch output.
Conclusions¶
playlist_indexdoes not exist underextract_flat. The spike as written inOPEN_QUESTIONS.mdassumed it would.PlaylistItemMeta.positionmust therefore be derived from enumeration order —enumerate(info["entries"], start=1)— and the P2.1 docstring claiming it "mirrors yt-dlp's 1-basedplaylist_index" is corrected in this PR. Enumeration order is the playlist order, so the derived value is the intended one; it is the provenance that was wrong, not the semantics.- A flat playlist extract cannot fill
descriptionorpublished_at.descriptionis not merelyNone— the key is absent from every entry, whiletimestampis present and alwaysNone. Either wayVideoMetakeeps its""/Nonedefaults for playlist-sourced rows; a full per-video extract is required to populate them. P2.2 must not fan out 183 full extracts duringfetch— the flat pass stays the default and--refreshon a single video does the deep one.PLAN.md§5.2'svideo showis therefore allowed to display an empty description for a video that was only ever seen through a playlist. durationdisagrees by ±1s between flat and full extracts (235 flat vs 234 full for the same video). A refresh that switches extraction mode will flip the stored value; that is upstream rounding, not a bug, and the upsert must not treat it as a meaningful change.channel_idon an entry is the video's own channel, not the playlist owner's. The playlist'schannel_idis on the top-level dict. Both are needed:playlist.channel_idfor ownership,video.channel_idfor the per-video row.thumbnailsin a flat extract top out at 336×188. A 1280×720 source thumbnail needs either the full extract'sthumbnail(maxresdefault.jpg) or the conventionali.ytimg.com/vi/<id>/maxresdefault.jpgURL. Relevant to P5.1, which fits a source thumbnail to 1280×720.
Fixture recorder consequence (P2.2)¶
The recorder must store the whole info dict for a playlist, including the top-level keys —
not just entries — because PlaylistMeta and the owning ChannelMeta are built from them.
Fixtures must cover both shapes: one flat playlist extract and one full single-video extract.