ytdl-org/youtube-dl · error · ExtractorError
unable to download video webpage: %s
Error message
unable to download video webpage: %s
What it means
SoundcloudSetIE resolves a playlist/sets URL through the Soundcloud API; if the resolved info dict contains an 'errors' list, each entry's 'error_message' is joined and raised as 'unable to download video webpage: <msgs>'. This surfaces Soundcloud's own playlist-level failures (most commonly 'Not Found' / 404 for deleted or private sets).
Source
Thrown at youtube_dl/extractor/soundcloud.py:554
}, {
'url': 'https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep/token',
'only_matching': True,
}]
def _real_extract(self, url):
mobj = re.match(self._VALID_URL, url)
full_title = '%s/sets/%s' % mobj.group('uploader', 'slug_title')
token = mobj.group('token')
if token:
full_title += '/' + token
info = self._download_json(self._resolv_url(
self._BASE_URL + full_title), full_title)
if 'errors' in info:
msgs = (compat_str(err['error_message']) for err in info['errors'])
raise ExtractorError('unable to download video webpage: %s' % ','.join(msgs))
return self._extract_set(info, token)
class SoundcloudPagedPlaylistBaseIE(SoundcloudIE):
def _extract_playlist(self, base_url, playlist_id, playlist_title):
# Per the SoundCloud documentation, the maximum limit for a linked partitioning query is 200.
# https://developers.soundcloud.com/blog/offset-pagination-deprecated
COMMON_QUERY = {
'limit': 200,
'linked_partitioning': '1',
}
query = COMMON_QUERY.copy()
query['offset'] = 0
next_href = base_url
View on GitHub (pinned to 956b8c5855)
Solutions
- Open the playlist URL in a browser to check whether it still exists / is public.
- If private, obtain the full share link including the secret token path segment and use that exact URL.
- If the uploader renamed, find their current handle and rebuild the URL.
- Update yt-dlp - resolver contract changes also surface through this error.
Defensive patterns
Strategy: try-catch
Validate before calling
info = resolve(full_title)
if 'errors' in info:
msgs = [e.get('error_message') for e in info['errors']]
if any('404' in str(m) or 'Not Found' in str(m) for m in msgs):
drop(url) Type guard
def has_resolver_errors(info: dict) -> bool:
return 'errors' in info and bool(info['errors']) Try / catch
try:
extract(url)
except ExtractorError as e:
if 'unable to download video webpage' in str(e):
check_playlist_visibility(url)
else:
raise Prevention
- Use full share URLs including the secret token for private sets.
- Refresh uploader handles when accounts are renamed.
- Treat resolver 404s as permanent; do not retry.
When it happens
Trigger: Resolving <uploader>/sets/<slug>[/<token>] where the API returns an errors array instead of playlist data: deleted playlist, private playlist accessed without its secret token, renamed uploader making the path stale.
Common situations: Deleted or made-private playlists (owner deleted the set or flipped visibility); a copied URL missing the secret token suffix; uploader renamed their account so the uploader segment of the URL no longer resolves; region/network blocks making the resolver return an error payload.
Related errors
- Invalid url for track %d of album url %s
- The page doesn't contain any tracks
- Gfycat said: %s
- Did not get a media key
- %s: No songs found, try using proxy
AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14).
Data as JSON: /api/errors/ae3ccf7cb903fe54.
Report an issue: GitHub.