]>
Commit | Line | Data |
---|---|---|
86e5f3ed | 1 | from .common import InfoExtractor |
12e022d0 TS |
2 | |
3 | from ..utils import ( | |
4 | ExtractorError, | |
5 | smuggle_url, | |
6 | str_or_none, | |
7 | traverse_obj, | |
8 | urlencode_postdata | |
9 | ) | |
10 | ||
11 | ||
12 | class CybraryBaseIE(InfoExtractor): | |
13 | _API_KEY = 'AIzaSyCX9ru6j70PX2My1Eq6Q1zoMAhuTdXlzSw' | |
14 | _ENDPOINTS = { | |
15 | 'course': 'https://app.cybrary.it/courses/api/catalog/browse/course/{}', | |
16 | 'course_enrollment': 'https://app.cybrary.it/courses/api/catalog/{}/enrollment', | |
17 | 'enrollment': 'https://app.cybrary.it/courses/api/enrollment/{}', | |
18 | 'launch': 'https://app.cybrary.it/courses/api/catalog/{}/launch', | |
19 | 'vimeo_oembed': 'https://vimeo.com/api/oembed.json?url=https://vimeo.com/{}', | |
20 | } | |
21 | _NETRC_MACHINE = 'cybrary' | |
22 | _TOKEN = None | |
23 | ||
24 | def _perform_login(self, username, password): | |
25 | CybraryBaseIE._TOKEN = self._download_json( | |
26 | f'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={self._API_KEY}', | |
27 | None, data=urlencode_postdata({'email': username, 'password': password, 'returnSecureToken': True}), | |
28 | note='Logging in')['idToken'] | |
29 | ||
30 | def _real_initialize(self): | |
31 | if not self._TOKEN: | |
32 | self.raise_login_required(method='password') | |
33 | ||
34 | def _call_api(self, endpoint, item_id): | |
35 | return self._download_json( | |
36 | self._ENDPOINTS[endpoint].format(item_id), item_id, | |
37 | note=f'Downloading {endpoint} JSON metadata', | |
38 | headers={'Authorization': f'Bearer {self._TOKEN}'}) | |
39 | ||
40 | def _get_vimeo_id(self, activity_id): | |
41 | launch_api = self._call_api('launch', activity_id) | |
42 | ||
43 | if launch_api.get('url'): | |
44 | return self._search_regex(r'https?://player\.vimeo\.com/video/(?P<vimeo_id>[0-9]+)', launch_api['url'], 'vimeo_id') | |
45 | return traverse_obj(launch_api, ('vendor_data', 'content', ..., 'videoId'), get_all=False) | |
46 | ||
47 | ||
48 | class CybraryIE(CybraryBaseIE): | |
49 | _VALID_URL = r'https?://app.cybrary.it/immersive/(?P<enrollment>[0-9]+)/activity/(?P<id>[0-9]+)' | |
50 | _TESTS = [{ | |
51 | 'url': 'https://app.cybrary.it/immersive/12487950/activity/63102', | |
52 | 'md5': '9ae12d37e555cb2ed554223a71a701d0', | |
53 | 'info_dict': { | |
54 | 'id': '646609770', | |
55 | 'ext': 'mp4', | |
56 | 'title': 'Getting Started', | |
57 | 'thumbnail': 'https://i.vimeocdn.com/video/1301817996-76a268f0c56cff18a5cecbbdc44131eb9dda0c80eb0b3a036_1280', | |
58 | 'series_id': '63111', | |
59 | 'uploader_url': 'https://vimeo.com/user30867300', | |
60 | 'duration': 88, | |
61 | 'uploader_id': 'user30867300', | |
62 | 'series': 'Cybrary Orientation', | |
63 | 'uploader': 'Cybrary', | |
64 | 'chapter': 'Cybrary Orientation Series', | |
65 | 'chapter_id': '63110' | |
66 | }, | |
67 | 'expected_warnings': ['No authenticators for vimeo'] | |
68 | }, { | |
69 | 'url': 'https://app.cybrary.it/immersive/12747143/activity/52686', | |
70 | 'md5': '62f26547dccc59c44363e2a13d4ad08d', | |
71 | 'info_dict': { | |
72 | 'id': '445638073', | |
73 | 'ext': 'mp4', | |
74 | 'title': 'Azure Virtual Network IP Addressing', | |
75 | 'thumbnail': 'https://i.vimeocdn.com/video/936667051-1647ace66c627d4a2382185e0dae8deb830309bfddd53f8b2367b2f91e92ed0e-d_1280', | |
76 | 'series_id': '52733', | |
77 | 'uploader_url': 'https://vimeo.com/user30867300', | |
78 | 'duration': 426, | |
79 | 'uploader_id': 'user30867300', | |
80 | 'series': 'AZ-500: Microsoft Azure Security Technologies', | |
81 | 'uploader': 'Cybrary', | |
82 | 'chapter': 'Implement Network Security', | |
83 | 'chapter_id': '52693' | |
84 | }, | |
85 | 'expected_warnings': ['No authenticators for vimeo'] | |
86 | }] | |
87 | ||
88 | def _real_extract(self, url): | |
89 | activity_id, enrollment_id = self._match_valid_url(url).group('id', 'enrollment') | |
90 | course = self._call_api('enrollment', enrollment_id)['content'] | |
91 | activity = traverse_obj(course, ('learning_modules', ..., 'activities', lambda _, v: int(activity_id) == v['id']), get_all=False) | |
92 | ||
93 | if activity.get('type') not in ['Video Activity', 'Lesson Activity']: | |
94 | raise ExtractorError('The activity is not a video', expected=True) | |
95 | ||
96 | module = next((m for m in course.get('learning_modules') or [] | |
97 | if int(activity_id) in traverse_obj(m, ('activities', ..., 'id') or [])), None) | |
98 | ||
99 | vimeo_id = self._get_vimeo_id(activity_id) | |
100 | ||
101 | return { | |
102 | '_type': 'url_transparent', | |
103 | 'series': traverse_obj(course, ('content_description', 'title')), | |
104 | 'series_id': str_or_none(traverse_obj(course, ('content_description', 'id'))), | |
105 | 'id': vimeo_id, | |
106 | 'chapter': module.get('title'), | |
107 | 'chapter_id': str_or_none(module.get('id')), | |
108 | 'title': activity.get('title'), | |
109 | 'url': smuggle_url(f'https://player.vimeo.com/video/{vimeo_id}', {'http_headers': {'Referer': 'https://api.cybrary.it'}}) | |
110 | } | |
111 | ||
112 | ||
113 | class CybraryCourseIE(CybraryBaseIE): | |
114 | _VALID_URL = r'https://app.cybrary.it/browse/course/(?P<id>[\w-]+)/?(?:$|[#?])' | |
115 | _TESTS = [{ | |
116 | 'url': 'https://app.cybrary.it/browse/course/az-500-microsoft-azure-security-technologies', | |
117 | 'info_dict': { | |
118 | 'id': 898, | |
119 | 'title': 'AZ-500: Microsoft Azure Security Technologies', | |
120 | 'description': 'md5:69549d379c0fc1dec92926d4e8b6fbd4' | |
121 | }, | |
122 | 'playlist_count': 59 | |
123 | }, { | |
124 | 'url': 'https://app.cybrary.it/browse/course/cybrary-orientation', | |
125 | 'info_dict': { | |
126 | 'id': 1245, | |
127 | 'title': 'Cybrary Orientation', | |
128 | 'description': 'md5:9e69ff66b32fe78744e0ad4babe2e88e' | |
129 | }, | |
130 | 'playlist_count': 4 | |
131 | }] | |
132 | ||
133 | def _real_extract(self, url): | |
134 | course_id = self._match_id(url) | |
135 | course = self._call_api('course', course_id) | |
136 | enrollment_info = self._call_api('course_enrollment', course['id']) | |
137 | ||
138 | entries = [self.url_result( | |
139 | f'https://app.cybrary.it/immersive/{enrollment_info["id"]}/activity/{activity["id"]}') | |
140 | for activity in traverse_obj(course, ('content_item', 'learning_modules', ..., 'activities', ...))] | |
141 | ||
142 | return self.playlist_result( | |
143 | entries, | |
144 | traverse_obj(course, ('content_item', 'id'), expected_type=str_or_none), | |
145 | course.get('title'), course.get('short_description')) |