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

  1. 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
  2. If the stream is currently live, use the live URL instead of the timeshift/archive URL
  3. Find a re-upload or the broadcaster's own archive elsewhere; the recording is gone from kukulu's servers
  4. 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

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


AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22). Data as JSON: /api/errors/483cf28d61eb8bc5. Report an issue: GitHub.