ytdl-org/youtube-dl · error · ExtractorError

No chapters found for course %s

Error message

No chapters found for course %s

What it means

Raised by the SafariCourseIE playlist extractor when the course JSON from the API has no 'chapters' key - there is nothing to enumerate into entries. Message interpolates the course id. Expected=True.

Source

Thrown at youtube_dl/extractor/safari.py:255

    }, {
        'url': 'https://www.oreilly.com/library/view/hadoop-fundamentals-livelessons/9780133392838/',
        'only_matching': True,
    }]

    @classmethod
    def suitable(cls, url):
        return (False if SafariIE.suitable(url) or SafariApiIE.suitable(url)
                else super(SafariCourseIE, cls).suitable(url))

    def _real_extract(self, url):
        course_id = self._match_id(url)

        course_json = self._download_json(
            '%s/book/%s/?override_format=%s' % (self._API_BASE, course_id, self._API_FORMAT),
            course_id, 'Downloading course JSON')

        if 'chapters' not in course_json:
            raise ExtractorError(
                'No chapters found for course %s' % course_id, expected=True)

        entries = [
            self.url_result(chapter, SafariApiIE.ie_key())
            for chapter in course_json['chapters']]

        course_title = course_json['title']

        return self.playlist_result(entries, course_id, course_title)

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Open the course page in a browser to confirm it still exists and contains video chapters.
  2. If it is a book (no video), there is nothing for youtube-dl to extract.
  3. Update youtube-dl in case the API response shape changed.
  4. If extracting single videos works, extract chapters individually instead of the whole course.
Defensive patterns

Strategy: validation

Validate before calling

# with the API accessible (same auth as the extractor)
def course_has_chapters(api_base, course_id, fmt):
    import json, urllib.request
    u = '%s/book/%s/?override_format=%s' % (api_base, course_id, fmt)
    with urllib.request.urlopen(u, timeout=10) as r:
        return 'chapters' in json.load(r)

Try / catch

try:
    ydl.extract_info(url)
except ExtractorError as e:
    if 'No chapters found for course' in str(e):
        skip(course_id)  # retired course or non-video book
    else:
        raise

Prevention

When it happens

Trigger: Downloading a safari course URL (library/view/<course>) whose /book/<id>/ API response lacks 'chapters': retired courses, non-video books, or API format changes (override_format parameter).

Common situations: Courses delisted from O'Reilly learning; URLs for book-only (non-video) titles; API contract changes after O'Reilly deploys.

Related errors


AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14). Data as JSON: /api/errors/4c7a938bce437fb9. Report an issue: GitHub.