ytdl-org/youtube-dl · error · ExtractorError
Failed to find episode data
Error message
Failed to find episode data
What it means
Raised by CallinIE when the parsed Next.js __NEXT_DATA__ JSON has no dict at props.pageProps.episode (traverse_obj returns None). The extractor relies entirely on that embedded structure to get title, m3u8 URL, and show metadata, so without it extraction cannot continue.
Source
Thrown at youtube_dl/extractor/callin.py:53
'channel_url': 'https://callin.com/show/the-debrief-with-briahna-joy-gray-siiFDzGegm',
}
}]
def _search_nextjs_data(self, webpage, video_id, transform_source=None, fatal=True, **kw):
return self._parse_json(
self._search_regex(
r'(?s)<script[^>]+id=[\'"]__NEXT_DATA__[\'"][^>]*>([^<]+)</script>',
webpage, 'next.js data', fatal=fatal, **kw),
video_id, transform_source=transform_source, fatal=fatal)
def _real_extract(self, url):
video_id = self._match_id(url)
webpage = self._download_webpage(url, video_id)
next_data = self._search_nextjs_data(webpage, video_id)
episode = traverse_obj(next_data, ('props', 'pageProps', 'episode'), expected_type=dict)
if not episode:
raise ExtractorError('Failed to find episode data')
title = episode.get('title') or self._og_search_title(webpage)
description = episode.get('description') or self._og_search_description(webpage)
formats = []
formats.extend(self._extract_m3u8_formats(
episode.get('m3u8'), video_id, 'mp4',
entry_protocol='m3u8_native', fatal=False))
self._sort_formats(formats)
channel = try_get(episode, lambda x: x['show']['title'], compat_str)
channel_url = try_get(episode, lambda x: x['show']['linkObj']['resourceUrl'], compat_str)
return {
'id': video_id,
'title': title,
'description': description,
'formats': formats,View on GitHub (pinned to 956b8c5855)
Solutions
- Confirm the URL is a real episode page (it should play in a plain browser).
- Open the page source and search for __NEXT_DATA__; if 'episode' is absent, the data layout changed — switch to yt-dlp or a newer extractor.
- If the show link redirects, follow the redirect and use the final URL.
- Report the breakage / patch the traverse path to wherever the episode object now lives.
Example fix
# example: make extraction resilient to a moved key
episode = (traverse_obj(next_data, ('props', 'pageProps', 'episode'), expected_type=dict)
or traverse_obj(next_data, ('props', 'pageProps', 'data', 'episode'), expected_type=dict)) Defensive patterns
Strategy: validation
Validate before calling
import json, re
def has_episode_data(html):
m = re.search(r'<script[^>]+id="__NEXT_DATA__"[^>]*>([^<]+)</script>', html)
if not m:
return False
data = json.loads(m.group(1))
return isinstance(data.get('props', {}).get('pageProps', {}).get('episode'), dict) Type guard
def is_episode_page(next_data):
ep = next_data.get('props', {}).get('pageProps', {}).get('episode') if isinstance(next_data, dict) else None
return isinstance(ep, dict) and bool(ep.get('m3u8')) Try / catch
from youtube_dl.utils import ExtractorError
try:
ydl.extract_info(url)
except ExtractorError as e:
if 'Failed to find episode data' in str(e):
mark_as_not_an_episode(url) # do not retry
else:
raise Prevention
- Validate the page contains a playable episode before queueing.
- Keep episode URLs (not hub/landing URLs) in your job list.
- Re-check page structure after site redesigns.
When it happens
Trigger: Downloading a callin.com URL that is not an episode page (a show/landing/login page), a page rendered differently (SSR structure changed or data moved), or an episode that was removed so pageProps no longer contains 'episode'.
Common situations: Site redesign moving the episode payload to a different key or to client-side fetching; passing a stub or preview URL; episode unlisted/deleted.
Related errors
- Failed to find IDEC id
- Unable to parse __NEXT_DATA__
- Invalid path
- Unauthorized user "%s"
- Missing "id" field in extractor result
AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14).
Data as JSON: /api/errors/0144d910de351118.
Report an issue: GitHub.