3 from .common
import InfoExtractor
15 class KhanAcademyBaseIE(InfoExtractor
):
16 _VALID_URL_TEMPL
= r
'https?://(?:www\.)?khanacademy\.org/(?P<id>(?:[^/]+/){%s}%s[^?#/&]+)'
18 _PUBLISHED_CONTENT_VERSION
= 'dc34750f0572c80f5effe7134082fe351143c1e4'
20 def _parse_video(self
, video
):
22 '_type': 'url_transparent',
23 'url': video
['youtubeId'],
24 'id': video
['youtubeId'],
26 **traverse_obj(video
, {
27 'display_id': ('id', {str_or_none}
),
28 'title': ('translatedTitle', {str}
),
29 'thumbnail': ('thumbnailUrls', ..., 'url', {url_or_none}
),
30 'duration': ('duration', {int_or_none}
),
31 'description': ('description', {str}
),
35 def _real_extract(self
, url
):
36 display_id
= self
._match
_id
(url
)
37 content
= self
._download
_json
(
38 'https://www.khanacademy.org/api/internal/graphql/ContentForPath', display_id
,
40 'fastly_cacheable': 'persist_until_publish',
41 'pcv': self
._PUBLISHED
_CONTENT
_VERSION
,
43 'variables': json
.dumps({
47 'clientPublishedContentVersion': self
._PUBLISHED
_CONTENT
_VERSION
,
50 })['data']['contentRoute']['listedPathData']
51 return self
._parse
_component
_props
(content
, display_id
)
54 class KhanAcademyIE(KhanAcademyBaseIE
):
55 IE_NAME
= 'khanacademy'
56 _VALID_URL
= KhanAcademyBaseIE
._VALID
_URL
_TEMPL
% ('4', 'v/')
58 'url': 'https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/one-time-pad',
59 'md5': '1d5c2e70fa6aa29c38eca419f12515ce',
63 'title': 'The one-time pad',
64 'description': 'The perfect cipher',
65 'display_id': '716378217',
67 'uploader': 'Khan Academy',
68 'uploader_id': '@khanacademy',
69 'uploader_url': 'https://www.youtube.com/@khanacademy',
70 'upload_date': '20120411',
71 'timestamp': 1334170113,
72 'license': 'cc-by-nc-sa',
73 'live_status': 'not_live',
74 'channel': 'Khan Academy',
75 'channel_id': 'UC4a-Gbdw7vOaccHmFo40b9g',
76 'channel_url': 'https://www.youtube.com/channel/UC4a-Gbdw7vOaccHmFo40b9g',
77 'channel_is_verified': True,
78 'playable_in_embed': True,
79 'categories': ['Education'],
80 'creators': ['Brit Cruise'],
83 'availability': 'public',
85 'channel_follower_count': int,
91 'add_ie': ['Youtube'],
94 def _parse_component_props(self
, component_props
, display_id
):
95 video
= component_props
['content']
97 **self
._parse
_video
(video
),
98 **traverse_obj(video
, {
99 'creators': ('authorNames', ..., {str}
),
100 'timestamp': ('dateAdded', {parse_iso8601}
),
101 'license': ('kaUserLicense', {str}
),
106 class KhanAcademyUnitIE(KhanAcademyBaseIE
):
107 IE_NAME
= 'khanacademy:unit'
108 _VALID_URL
= (KhanAcademyBaseIE
._VALID
_URL
_TEMPL
% ('1,2', '')) + '/?(?:[?#&]|$)'
110 'url': 'https://www.khanacademy.org/computing/computer-science/cryptography',
113 'title': 'Cryptography',
114 'description': 'How have humans protected their secret messages through history? What has changed today?',
115 'display_id': 'computing/computer-science/cryptography',
116 '_old_archive_ids': ['khanacademyunit cryptography'],
118 'playlist_mincount': 31,
120 'url': 'https://www.khanacademy.org/computing/computer-science',
123 'title': 'Computer science theory',
124 'description': 'md5:4b472a4646e6cf6ec4ccb52c4062f8ba',
125 'display_id': 'computing/computer-science',
126 '_old_archive_ids': ['khanacademyunit computer-science'],
128 'playlist_mincount': 50,
131 def _parse_component_props(self
, component_props
, display_id
):
132 course
= component_props
['course']
133 selected_unit
= traverse_obj(course
, (
134 'unitChildren', lambda _
, v
: v
['relativeUrl'] == f
'/{display_id}', any
)) or course
136 def build_entry(entry
):
137 return self
.url_result(urljoin(
138 'https://www.khanacademy.org', entry
['canonicalUrl']),
139 KhanAcademyIE
, title
=entry
.get('translatedTitle'))
141 entries
= traverse_obj(selected_unit
, (
142 (('unitChildren', ...), None), 'allOrderedChildren', ..., 'curatedChildren',
143 lambda _
, v
: v
['contentKind'] == 'Video' and v
['canonicalUrl'], {build_entry}
))
145 return self
.playlist_result(
147 display_id
=display_id
,
148 **traverse_obj(selected_unit
, {
150 'title': ('translatedTitle', {str}
),
151 'description': ('translatedDescription', {str}
),
152 '_old_archive_ids': ('slug', {str}
, {lambda x
: [make_archive_id(self
, x
)] if x
else None}),