summaryrefslogtreecommitdiff
path: root/youtube_dl/extractor/egghead.py
blob: 9bbd703e072ea3e78c5ec71b44bdd42812a41259 (plain)
    1 # coding: utf-8
    2 from __future__ import unicode_literals
    3 
    4 from .common import InfoExtractor
    5 from ..compat import compat_str
    6 from ..utils import (
    7     determine_ext,
    8     int_or_none,
    9     try_get,
   10     unified_timestamp,
   11     url_or_none,
   12 )
   13 
   14 
   15 class EggheadBaseIE(InfoExtractor):
   16     def _call_api(self, path, video_id, resource, fatal=True):
   17         return self._download_json(
   18             'https://app.egghead.io/api/v1/' + path,
   19             video_id, 'Downloading %s JSON' % resource, fatal=fatal)
   20 
   21 
   22 class EggheadCourseIE(EggheadBaseIE):
   23     IE_DESC = 'egghead.io course'
   24     IE_NAME = 'egghead:course'
   25     _VALID_URL = r'https://(?:app\.)?egghead\.io/(?:course|playlist)s/(?P<id>[^/?#&]+)'
   26     _TESTS = [{
   27         'url': 'https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript',
   28         'playlist_count': 29,
   29         'info_dict': {
   30             'id': '432655',
   31             'title': 'Professor Frisby Introduces Composable Functional JavaScript',
   32             'description': 're:(?s)^This course teaches the ubiquitous.*You\'ll start composing functionality before you know it.$',
   33         },
   34     }, {
   35         'url': 'https://app.egghead.io/playlists/professor-frisby-introduces-composable-functional-javascript',
   36         'only_matching': True,
   37     }]
   38 
   39     def _real_extract(self, url):
   40         playlist_id = self._match_id(url)
   41         series_path = 'series/' + playlist_id
   42         lessons = self._call_api(
   43             series_path + '/lessons', playlist_id, 'course lessons')
   44 
   45         entries = []
   46         for lesson in lessons:
   47             lesson_url = url_or_none(lesson.get('http_url'))
   48             if not lesson_url:
   49                 continue
   50             lesson_id = lesson.get('id')
   51             if lesson_id:
   52                 lesson_id = compat_str(lesson_id)
   53             entries.append(self.url_result(
   54                 lesson_url, ie=EggheadLessonIE.ie_key(), video_id=lesson_id))
   55 
   56         course = self._call_api(
   57             series_path, playlist_id, 'course', False) or {}
   58 
   59         playlist_id = course.get('id')
   60         if playlist_id:
   61             playlist_id = compat_str(playlist_id)
   62 
   63         return self.playlist_result(
   64             entries, playlist_id, course.get('title'),
   65             course.get('description'))
   66 
   67 
   68 class EggheadLessonIE(EggheadBaseIE):
   69     IE_DESC = 'egghead.io lesson'
   70     IE_NAME = 'egghead:lesson'
   71     _VALID_URL = r'https://(?:app\.)?egghead\.io/(?:api/v1/)?lessons/(?P<id>[^/?#&]+)'
   72     _TESTS = [{
   73         'url': 'https://egghead.io/lessons/javascript-linear-data-flow-with-container-style-types-box',
   74         'info_dict': {
   75             'id': '1196',
   76             'display_id': 'javascript-linear-data-flow-with-container-style-types-box',
   77             'ext': 'mp4',
   78             'title': 'Create linear data flow with container style types (Box)',
   79             'description': 'md5:9aa2cdb6f9878ed4c39ec09e85a8150e',
   80             'thumbnail': r're:^https?:.*\.jpg$',
   81             'timestamp': 1481296768,
   82             'upload_date': '20161209',
   83             'duration': 304,
   84             'view_count': 0,
   85             'tags': 'count:2',
   86         },
   87         'params': {
   88             'skip_download': True,
   89             'format': 'bestvideo',
   90         },
   91     }, {
   92         'url': 'https://egghead.io/api/v1/lessons/react-add-redux-to-a-react-application',
   93         'only_matching': True,
   94     }, {
   95         'url': 'https://app.egghead.io/lessons/javascript-linear-data-flow-with-container-style-types-box',
   96         'only_matching': True,
   97     }]
   98 
   99     def _real_extract(self, url):
  100         display_id = self._match_id(url)
  101 
  102         lesson = self._call_api(
  103             'lessons/' + display_id, display_id, 'lesson')
  104 
  105         lesson_id = compat_str(lesson['id'])
  106         title = lesson['title']
  107 
  108         formats = []
  109         for _, format_url in lesson['media_urls'].items():
  110             format_url = url_or_none(format_url)
  111             if not format_url:
  112                 continue
  113             ext = determine_ext(format_url)
  114             if ext == 'm3u8':
  115                 formats.extend(self._extract_m3u8_formats(
  116                     format_url, lesson_id, 'mp4', entry_protocol='m3u8',
  117                     m3u8_id='hls', fatal=False))
  118             elif ext == 'mpd':
  119                 formats.extend(self._extract_mpd_formats(
  120                     format_url, lesson_id, mpd_id='dash', fatal=False))
  121             else:
  122                 formats.append({
  123                     'url': format_url,
  124                 })
  125         self._sort_formats(formats)
  126 
  127         return {
  128             'id': lesson_id,
  129             'display_id': display_id,
  130             'title': title,
  131             'description': lesson.get('summary'),
  132             'thumbnail': lesson.get('thumb_nail'),
  133             'timestamp': unified_timestamp(lesson.get('published_at')),
  134             'duration': int_or_none(lesson.get('duration')),
  135             'view_count': int_or_none(lesson.get('plays_count')),
  136             'tags': try_get(lesson, lambda x: x['tag_list'], list),
  137             'series': try_get(
  138                 lesson, lambda x: x['series']['title'], compat_str),
  139             'formats': formats,
  140         }

Generated by cgit