yt-dlp/yt-dlp · error · ExtractorError
This stream has expired
Error message
This stream has expired
What it means
Raised by the kukulu.live extractor when the stream page contains the Japanese marker 'タイムシフトが見つかりませんでした。' ('timeshift not found'). kukulu.live only archives live streams as 'timeshift' recordings for a retention period chosen by the broadcaster; once that window passes (or recording was never enabled), the URL serves an error page and extraction stops. It is raised with expected=True, so yt-dlp reports it as a clean, user-facing 'video unavailable' rather than a bug.
Source
Thrown at yt_dlp/extractor/kukululive.py:88
'ext': 'mp4',
'vcodec': vcodec,
'quality': quality_priority,
})
if traverse_obj(quality_meta, ('hlsaddr_audioonly', 0, {url_or_none})):
formats.append({
'format_id': join_nonempty(quality, 'audioonly'),
'url': quality_meta['hlsaddr_audioonly'][0],
'ext': 'm4a',
'vcodec': 'none',
'quality': quality_priority,
})
def _real_extract(self, url):
video_id = self._match_id(url)
html = self._download_webpage(url, video_id)
if '>タイムシフトが見つかりませんでした。<' in html:
raise ExtractorError('This stream has expired', expected=True)
title = clean_html(
get_element_by_id('livetitle', html.replace('<SPAN', '<span').replace('SPAN>', 'span>')))
description = self._html_search_meta('Description', html)
thumbnail = self._html_search_meta(['og:image', 'twitter:image'], html)
if self._search_regex(r'(var\s+timeshift\s*=\s*false)', html, 'is livestream', default=False):
formats = []
for (desc, code) in [('high', 'Z'), ('low', 'ForceLow')]:
quality_meta = self._get_quality_meta(video_id, desc, code)
self._add_quality_formats(formats, quality_meta)
if desc == 'high' and traverse_obj(quality_meta, ('vcodec', 0)) == 'HEVC':
self._add_quality_formats(
formats, self._get_quality_meta(video_id, desc, code, force_h264='1'))
return {
'id': video_id,
'title': title,View on GitHub (pinned to 81ecd58b13)
Solutions
- Confirm in a browser that the URL really shows the 'timeshift not found' page; if the recording plays in a browser, run yt-dlp -U and retry
- If the stream is currently live, use the live URL instead of the timeshift/archive URL
- Find a re-upload or the broadcaster's own archive elsewhere; the recording is gone from kukulu's servers
- For automation, treat this message as a permanent 'not available' signal and stop retrying the URL
Defensive patterns
Strategy: try-catch
Type guard
def is_kukulu_expired(err) -> bool:
from yt_dlp.utils import ExtractorError
return isinstance(err, ExtractorError) and 'This stream has expired' in str(err) Try / catch
from yt_dlp.utils import ExtractorError
try:
info = ydl.extract_info(url, download=True)
except ExtractorError as e:
if e.expected and 'This stream has expired' in str(e):
mark_permanently_unavailable(url) # do not retry; recording is gone
else:
raise Prevention
- Extract kukulu timeshifts promptly after the broadcast; retention is set per-broadcaster and can be short
- If the content matters, keep your own recording instead of relying on timeshift availability
- In batch pipelines, cache known-expired URLs to avoid re-hitting them
When it happens
Trigger: Running extraction on a live.kukulu.live (/archive/) URL after _download_webpage() when the HTML contains '>タイムシフトが見つかりませんでした。<'. Happens when the timeshift retention elapsed, the broadcaster deleted the recording, or the stream was live-only and never recorded.
Common situations: Old stream links pasted in chats, blogs, or issue trackers months after the broadcast; broadcaster set short/no retention; stream removed by moderator; automated batch jobs processing historical URLs.
Related errors
- {self.IE_NAME} said: {expired}
- This video is not available yet. Release date: {start_date}
- {error}
- Content is not a video/podcast
- Bigo says: {} (code {})
AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22).
Data as JSON: /api/errors/483cf28d61eb8bc5.
Report an issue: GitHub.