3 from .common
import InfoExtractor
4 from .vimeo
import VimeoIE
16 class RayWenderlichIE(InfoExtractor
):
20 videos\.raywenderlich\.com/courses|
21 (?:www\.)?raywenderlich\.com
23 (?P<course_id>[^/]+)/lessons/(?P<id>\d+)
27 'url': 'https://www.raywenderlich.com/3530-testing-in-ios/lessons/1',
31 'title': 'Introduction',
32 'description': 'md5:804d031b3efa9fcb49777d512d74f722',
33 'timestamp': 1513906277,
34 'upload_date': '20171222',
36 'uploader': 'Ray Wenderlich',
37 'uploader_id': 'user3304672',
41 'skip_download': True,
43 'add_ie': [VimeoIE
.ie_key()],
44 'expected_warnings': ['HTTP Error 403: Forbidden'],
46 'url': 'https://videos.raywenderlich.com/courses/105-testing-in-ios/lessons/1',
47 'only_matching': True,
51 def _extract_video_id(data
, lesson_id
):
54 groups
= try_get(data
, lambda x
: x
['groups'], list) or []
58 if not isinstance(group
, dict):
60 contents
= try_get(data
, lambda x
: x
['contents'], list) or []
61 for content
in contents
:
62 if not isinstance(content
, dict):
64 ordinal
= int_or_none(content
.get('ordinal'))
65 if ordinal
!= lesson_id
:
67 video_id
= content
.get('identifier')
71 def _real_extract(self
, url
):
72 mobj
= self
._match
_valid
_url
(url
)
73 course_id
, lesson_id
= mobj
.group('course_id', 'id')
74 display_id
= f
'{course_id}/{lesson_id}'
76 webpage
= self
._download
_webpage
(url
, display_id
)
78 thumbnail
= self
._og
_search
_thumbnail
(
79 webpage
, default
=None) or self
._html
_search
_meta
(
80 'twitter:image', webpage
, 'thumbnail')
82 if '>Subscribe to unlock' in webpage
:
84 'This content is only available for subscribers',
88 'thumbnail': thumbnail
,
91 vimeo_id
= self
._search
_regex
(
92 r
'data-vimeo-id=["\'](\d
+)', webpage, 'vimeo
id', default=None)
95 data = self._parse_json(
97 r'data
-collection
=(["\'])(?P<data>{.+?})\1', webpage,
98 'data collection', default='{}', group='data'),
99 display_id, transform_source=unescapeHTML, fatal=False)
100 video_id = self._extract_video_id(
101 data, lesson_id) or self._search_regex(
102 r'/videos/(\d+)/', thumbnail, 'video id')
105 'X-Requested-With': 'XMLHttpRequest',
107 csrf_token = self._html_search_meta(
108 'csrf-token', webpage, 'csrf token', default=None)
110 headers['X-CSRF-Token'] = csrf_token
111 video = self._download_json(
112 f'https://videos.raywenderlich.com/api/v1/videos/{video_id}.json',
113 display_id, headers=headers)['video']
114 vimeo_id = video['clips'][0]['provider_id']
116 '_type': 'url_transparent',
117 'title': video.get('name'),
118 'description': video.get('description') or video.get(
120 'duration': int_or_none(video.get('duration')),
121 'timestamp': unified_timestamp(video.get('created_at')),
124 return merge_dicts(info, self.url_result(
125 VimeoIE._smuggle_referrer(
126 f'https://player.vimeo.com/video/{vimeo_id}', url),
127 ie=VimeoIE.ie_key(), video_id=vimeo_id))
130 class RayWenderlichCourseIE(InfoExtractor):
131 _VALID_URL = r'''(?x)
134 videos\.raywenderlich\.com/courses|
135 (?:www\.)?raywenderlich\.com
141 'url': 'https://www.raywenderlich.com/3530-testing-in-ios',
143 'title': 'Testing in iOS',
144 'id': '3530-testing-in-ios',
149 'playlist_count': 29,
153 def suitable(cls, url):
154 return False if RayWenderlichIE.suitable(url) else super().suitable(url)
156 def _real_extract(self, url):
157 course_id = self._match_id(url)
159 webpage = self._download_webpage(url, course_id)
163 for lesson_url in re.findall(
164 rf'<a[^>]+\bhref=["\'](/{course_id}
/lessons
/\d
+)', webpage):
165 if lesson_url in lesson_urls:
167 lesson_urls.add(lesson_url)
168 entries.append(self.url_result(
169 urljoin(url, lesson_url), ie=RayWenderlichIE.ie_key()))
171 title = self._og_search_title(
172 webpage, default=None) or self._html_search_meta(
173 'twitter
:title
', webpage, 'title
', default=None)
175 return self.playlist_result(entries, course_id, title)