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