{"record":{"id":"4c7a938bce437fb9","repo":"ytdl-org/youtube-dl","slug":"no-chapters-found-for-course-s","errorCode":null,"errorMessage":"No chapters found for course %s","messagePattern":"No chapters found for course (.+?)","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"youtube_dl/extractor/safari.py","lineNumber":255,"sourceCode":"    }, {\n        'url': 'https://www.oreilly.com/library/view/hadoop-fundamentals-livelessons/9780133392838/',\n        'only_matching': True,\n    }]\n\n    @classmethod\n    def suitable(cls, url):\n        return (False if SafariIE.suitable(url) or SafariApiIE.suitable(url)\n                else super(SafariCourseIE, cls).suitable(url))\n\n    def _real_extract(self, url):\n        course_id = self._match_id(url)\n\n        course_json = self._download_json(\n            '%s/book/%s/?override_format=%s' % (self._API_BASE, course_id, self._API_FORMAT),\n            course_id, 'Downloading course JSON')\n\n        if 'chapters' not in course_json:\n            raise ExtractorError(\n                'No chapters found for course %s' % course_id, expected=True)\n\n        entries = [\n            self.url_result(chapter, SafariApiIE.ie_key())\n            for chapter in course_json['chapters']]\n\n        course_title = course_json['title']\n\n        return self.playlist_result(entries, course_id, course_title)\n","sourceCodeStart":237,"sourceCodeEnd":265,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/safari.py#L237-L265","documentation":"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.","triggerScenarios":"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).","commonSituations":"Courses delisted from O'Reilly learning; URLs for book-only (non-video) titles; API contract changes after O'Reilly deploys.","solutions":["Open the course page in a browser to confirm it still exists and contains video chapters.","If it is a book (no video), there is nothing for youtube-dl to extract.","Update youtube-dl in case the API response shape changed.","If extracting single videos works, extract chapters individually instead of the whole course."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# with the API accessible (same auth as the extractor)\ndef course_has_chapters(api_base, course_id, fmt):\n    import json, urllib.request\n    u = '%s/book/%s/?override_format=%s' % (api_base, course_id, fmt)\n    with urllib.request.urlopen(u, timeout=10) as r:\n        return 'chapters' in json.load(r)","typeGuard":null,"tryCatchPattern":"try:\n    ydl.extract_info(url)\nexcept ExtractorError as e:\n    if 'No chapters found for course' in str(e):\n        skip(course_id)  # retired course or non-video book\n    else:\n        raise","preventionTips":["Verify in a browser that the course exists and actually contains video chapters.","Book-only (non-video) O'Reilly titles will always fail here - filter them before queueing.","If single-video extraction works, fall back to per-chapter URLs."],"tags":["safari","oreilly","course","playlist","expected-error"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}