]>
Commit | Line | Data |
---|---|---|
df228355 | 1 | import itertools |
cb10cded PH |
2 | import re |
3 | ||
4 | from .common import InfoExtractor | |
5 | from ..utils import ( | |
e897bd82 | 6 | ExtractorError, |
85552042 | 7 | clean_html, |
fea92aa6 | 8 | determine_ext, |
065c4b27 | 9 | dict_get, |
df228355 | 10 | extract_attributes, |
cc2db878 | 11 | float_or_none, |
ccb079ee | 12 | int_or_none, |
51378d35 | 13 | parse_duration, |
cc2db878 | 14 | str_or_none, |
fea92aa6 | 15 | try_get, |
44731e30 | 16 | unified_strdate, |
3052a30d | 17 | url_or_none, |
cc2db878 | 18 | urljoin, |
cb10cded PH |
19 | ) |
20 | ||
21 | ||
22 | class XHamsterIE(InfoExtractor): | |
45b2ee6f | 23 | _DOMAINS = r'(?:xhamster\.(?:com|one|desi)|xhms\.pro|xhamster\d+\.com|xhday\.com|xhvid\.com)' |
add96eb9 | 24 | _VALID_URL = rf'''(?x) |
00e5c363 | 25 | https?:// |
add96eb9 | 26 | (?:[^/?#]+\.)?{_DOMAINS}/ |
00e5c363 | 27 | (?: |
6cb30ea5 J |
28 | movies/(?P<id>[\dA-Za-z]+)/(?P<display_id>[^/]*)\.html| |
29 | videos/(?P<display_id_2>[^/]*)-(?P<id_2>[\dA-Za-z]+) | |
00e5c363 | 30 | ) |
add96eb9 | 31 | ''' |
6b43132c | 32 | _TESTS = [{ |
8945b10f | 33 | 'url': 'https://xhamster.com/videos/femaleagent-shy-beauty-takes-the-bait-1509445', |
6d1b3489 | 34 | 'md5': '34e1ab926db5dc2750fed9e1f34304bb', |
6b43132c S |
35 | 'info_dict': { |
36 | 'id': '1509445', | |
8945b10f | 37 | 'display_id': 'femaleagent-shy-beauty-takes-the-bait', |
6b43132c S |
38 | 'ext': 'mp4', |
39 | 'title': 'FemaleAgent Shy beauty takes the bait', | |
fea92aa6 | 40 | 'timestamp': 1350194821, |
6b43132c S |
41 | 'upload_date': '20121014', |
42 | 'uploader': 'Ruseful2011', | |
6d1b3489 | 43 | 'uploader_id': 'ruseful2011', |
51378d35 | 44 | 'duration': 893, |
6b43132c | 45 | 'age_limit': 18, |
ccb079ee | 46 | }, |
6b43132c | 47 | }, { |
8945b10f | 48 | 'url': 'https://xhamster.com/videos/britney-spears-sexy-booty-2221348?hd=', |
6b43132c S |
49 | 'info_dict': { |
50 | 'id': '2221348', | |
8945b10f | 51 | 'display_id': 'britney-spears-sexy-booty', |
6b43132c S |
52 | 'ext': 'mp4', |
53 | 'title': 'Britney Spears Sexy Booty', | |
fea92aa6 | 54 | 'timestamp': 1379123460, |
6b43132c S |
55 | 'upload_date': '20130914', |
56 | 'uploader': 'jojo747400', | |
51378d35 | 57 | 'duration': 200, |
6b43132c | 58 | 'age_limit': 18, |
5b9aefef | 59 | }, |
6b43132c S |
60 | 'params': { |
61 | 'skip_download': True, | |
a4690b32 | 62 | }, |
6b43132c | 63 | }, { |
8945b10f | 64 | # empty seo, unavailable via new URL schema |
6b43132c S |
65 | 'url': 'http://xhamster.com/movies/5667973/.html', |
66 | 'info_dict': { | |
67 | 'id': '5667973', | |
68 | 'ext': 'mp4', | |
69 | 'title': '....', | |
fea92aa6 | 70 | 'timestamp': 1454948101, |
6b43132c S |
71 | 'upload_date': '20160208', |
72 | 'uploader': 'parejafree', | |
6d1b3489 | 73 | 'uploader_id': 'parejafree', |
51378d35 | 74 | 'duration': 72, |
6b43132c | 75 | 'age_limit': 18, |
5b9aefef | 76 | }, |
6b43132c S |
77 | 'params': { |
78 | 'skip_download': True, | |
79 | }, | |
b271e335 W |
80 | }, { |
81 | # mobile site | |
82 | 'url': 'https://m.xhamster.com/videos/cute-teen-jacqueline-solo-masturbation-8559111', | |
83 | 'only_matching': True, | |
6b43132c S |
84 | }, { |
85 | 'url': 'https://xhamster.com/movies/2272726/amber_slayed_by_the_knight.html', | |
86 | 'only_matching': True, | |
103f8c8d S |
87 | }, { |
88 | # This video is visible for marcoalfa123456's friends only | |
89 | 'url': 'https://it.xhamster.com/movies/7263980/la_mia_vicina.html', | |
90 | 'only_matching': True, | |
00e5c363 S |
91 | }, { |
92 | # new URL schema | |
93 | 'url': 'https://pt.xhamster.com/videos/euro-pedal-pumping-7937821', | |
94 | 'only_matching': True, | |
b43c5f47 S |
95 | }, { |
96 | 'url': 'https://xhamster.one/videos/femaleagent-shy-beauty-takes-the-bait-1509445', | |
97 | 'only_matching': True, | |
8945b10f S |
98 | }, { |
99 | 'url': 'https://xhamster.desi/videos/femaleagent-shy-beauty-takes-the-bait-1509445', | |
100 | 'only_matching': True, | |
101 | }, { | |
102 | 'url': 'https://xhamster2.com/videos/femaleagent-shy-beauty-takes-the-bait-1509445', | |
103 | 'only_matching': True, | |
10709fc7 S |
104 | }, { |
105 | 'url': 'https://xhamster11.com/videos/femaleagent-shy-beauty-takes-the-bait-1509445', | |
106 | 'only_matching': True, | |
107 | }, { | |
108 | 'url': 'https://xhamster26.com/videos/femaleagent-shy-beauty-takes-the-bait-1509445', | |
109 | 'only_matching': True, | |
8945b10f S |
110 | }, { |
111 | 'url': 'http://xhamster.com/movies/1509445/femaleagent_shy_beauty_takes_the_bait.html', | |
112 | 'only_matching': True, | |
113 | }, { | |
114 | 'url': 'http://xhamster.com/movies/2221348/britney_spears_sexy_booty.html?hd', | |
115 | 'only_matching': True, | |
6cb30ea5 J |
116 | }, { |
117 | 'url': 'http://de.xhamster.com/videos/skinny-girl-fucks-herself-hard-in-the-forest-xhnBJZx', | |
118 | 'only_matching': True, | |
6d1b3489 | 119 | }, { |
120 | 'url': 'https://xhday.com/videos/strapless-threesome-xhh7yVf', | |
121 | 'only_matching': True, | |
45b2ee6f | 122 | }, { |
123 | 'url': 'https://xhvid.com/videos/lk-mm-xhc6wn6', | |
124 | 'only_matching': True, | |
6b43132c | 125 | }] |
cb10cded | 126 | |
5f6a1245 | 127 | def _real_extract(self, url): |
5ad28e7f | 128 | mobj = self._match_valid_url(url) |
00e5c363 S |
129 | video_id = mobj.group('id') or mobj.group('id_2') |
130 | display_id = mobj.group('display_id') or mobj.group('display_id_2') | |
131 | ||
b271e335 | 132 | desktop_url = re.sub(r'^(https?://(?:.+?\.)?)m\.', r'\1', url) |
b274e48d | 133 | webpage, urlh = self._download_webpage_handle(desktop_url, video_id) |
cb10cded | 134 | |
103f8c8d S |
135 | error = self._html_search_regex( |
136 | r'<div[^>]+id=["\']videoClosed["\'][^>]*>(.+?)</div>', | |
137 | webpage, 'error', default=None) | |
138 | if error: | |
139 | raise ExtractorError(error, expected=True) | |
140 | ||
fea92aa6 S |
141 | age_limit = self._rta_search(webpage) |
142 | ||
143 | def get_height(s): | |
144 | return int_or_none(self._search_regex( | |
145 | r'^(\d+)[pP]', s, 'height', default=None)) | |
146 | ||
147 | initials = self._parse_json( | |
148 | self._search_regex( | |
39e7107d U |
149 | (r'window\.initials\s*=\s*({.+?})\s*;\s*</script>', |
150 | r'window\.initials\s*=\s*({.+?})\s*;'), webpage, 'initials', | |
fea92aa6 S |
151 | default='{}'), |
152 | video_id, fatal=False) | |
153 | if initials: | |
154 | video = initials['videoModel'] | |
155 | title = video['title'] | |
156 | formats = [] | |
cc2db878 | 157 | format_urls = set() |
158 | format_sizes = {} | |
159 | sources = try_get(video, lambda x: x['sources'], dict) or {} | |
160 | for format_id, formats_dict in sources.items(): | |
fea92aa6 S |
161 | if not isinstance(formats_dict, dict): |
162 | continue | |
cc2db878 | 163 | download_sources = try_get(sources, lambda x: x['download'], dict) or {} |
164 | for quality, format_dict in download_sources.items(): | |
165 | if not isinstance(format_dict, dict): | |
166 | continue | |
167 | format_sizes[quality] = float_or_none(format_dict.get('size')) | |
fea92aa6 S |
168 | for quality, format_item in formats_dict.items(): |
169 | if format_id == 'download': | |
170 | # Download link takes some time to be generated, | |
171 | # skipping for now | |
172 | continue | |
cc2db878 | 173 | format_url = format_item |
3052a30d | 174 | format_url = url_or_none(format_url) |
cc2db878 | 175 | if not format_url or format_url in format_urls: |
fea92aa6 | 176 | continue |
cc2db878 | 177 | format_urls.add(format_url) |
fea92aa6 | 178 | formats.append({ |
add96eb9 | 179 | 'format_id': f'{format_id}-{quality}', |
fea92aa6 S |
180 | 'url': format_url, |
181 | 'ext': determine_ext(format_url, 'mp4'), | |
182 | 'height': get_height(quality), | |
cc2db878 | 183 | 'filesize': format_sizes.get(quality), |
b274e48d | 184 | 'http_headers': { |
3d2623a8 | 185 | 'Referer': urlh.url, |
b274e48d | 186 | }, |
fea92aa6 | 187 | }) |
cc2db878 | 188 | xplayer_sources = try_get( |
189 | initials, lambda x: x['xplayerSettings']['sources'], dict) | |
190 | if xplayer_sources: | |
191 | hls_sources = xplayer_sources.get('hls') | |
192 | if isinstance(hls_sources, dict): | |
193 | for hls_format_key in ('url', 'fallback'): | |
194 | hls_url = hls_sources.get(hls_format_key) | |
195 | if not hls_url: | |
196 | continue | |
197 | hls_url = urljoin(url, hls_url) | |
198 | if not hls_url or hls_url in format_urls: | |
199 | continue | |
200 | format_urls.add(hls_url) | |
201 | formats.extend(self._extract_m3u8_formats( | |
202 | hls_url, video_id, 'mp4', entry_protocol='m3u8_native', | |
203 | m3u8_id='hls', fatal=False)) | |
204 | standard_sources = xplayer_sources.get('standard') | |
205 | if isinstance(standard_sources, dict): | |
206 | for format_id, formats_list in standard_sources.items(): | |
207 | if not isinstance(formats_list, list): | |
208 | continue | |
209 | for standard_format in formats_list: | |
210 | if not isinstance(standard_format, dict): | |
211 | continue | |
212 | for standard_format_key in ('url', 'fallback'): | |
213 | standard_url = standard_format.get(standard_format_key) | |
214 | if not standard_url: | |
215 | continue | |
216 | standard_url = urljoin(url, standard_url) | |
217 | if not standard_url or standard_url in format_urls: | |
218 | continue | |
219 | format_urls.add(standard_url) | |
220 | ext = determine_ext(standard_url, 'mp4') | |
221 | if ext == 'm3u8': | |
222 | formats.extend(self._extract_m3u8_formats( | |
223 | standard_url, video_id, 'mp4', entry_protocol='m3u8_native', | |
224 | m3u8_id='hls', fatal=False)) | |
225 | continue | |
226 | quality = (str_or_none(standard_format.get('quality')) | |
227 | or str_or_none(standard_format.get('label')) | |
228 | or '') | |
229 | formats.append({ | |
add96eb9 | 230 | 'format_id': f'{format_id}-{quality}', |
cc2db878 | 231 | 'url': standard_url, |
232 | 'ext': ext, | |
233 | 'height': get_height(quality), | |
234 | 'filesize': format_sizes.get(quality), | |
235 | 'http_headers': { | |
236 | 'Referer': standard_url, | |
237 | }, | |
238 | }) | |
fea92aa6 S |
239 | |
240 | categories_list = video.get('categories') | |
241 | if isinstance(categories_list, list): | |
242 | categories = [] | |
243 | for c in categories_list: | |
244 | if not isinstance(c, dict): | |
245 | continue | |
246 | c_name = c.get('name') | |
add96eb9 | 247 | if isinstance(c_name, str): |
fea92aa6 S |
248 | categories.append(c_name) |
249 | else: | |
250 | categories = None | |
251 | ||
908b56ea | 252 | uploader_url = url_or_none(try_get(video, lambda x: x['author']['pageURL'])) |
fea92aa6 S |
253 | return { |
254 | 'id': video_id, | |
255 | 'display_id': display_id, | |
256 | 'title': title, | |
257 | 'description': video.get('description'), | |
258 | 'timestamp': int_or_none(video.get('created')), | |
259 | 'uploader': try_get( | |
add96eb9 | 260 | video, lambda x: x['author']['name'], str), |
908b56ea | 261 | 'uploader_url': uploader_url, |
262 | 'uploader_id': uploader_url.split('/')[-1] if uploader_url else None, | |
fea92aa6 S |
263 | 'thumbnail': video.get('thumbURL'), |
264 | 'duration': int_or_none(video.get('duration')), | |
265 | 'view_count': int_or_none(video.get('views')), | |
266 | 'like_count': int_or_none(try_get( | |
267 | video, lambda x: x['rating']['likes'], int)), | |
268 | 'dislike_count': int_or_none(try_get( | |
269 | video, lambda x: x['rating']['dislikes'], int)), | |
270 | 'comment_count': int_or_none(video.get('views')), | |
6d1b3489 | 271 | 'age_limit': age_limit if age_limit is not None else 18, |
fea92aa6 S |
272 | 'categories': categories, |
273 | 'formats': formats, | |
274 | } | |
275 | ||
276 | # Old layout fallback | |
277 | ||
4395ca2e | 278 | title = self._html_search_regex( |
1a6d9284 S |
279 | [r'<h1[^>]*>([^<]+)</h1>', |
280 | r'<meta[^>]+itemprop=".*?caption.*?"[^>]+content="(.+?)"', | |
281 | r'<title[^>]*>(.+?)(?:,\s*[^,]*?\s*Porn\s*[^,]*?:\s*xHamster[^<]*| - xHamster\.com)</title>'], | |
282 | webpage, 'title') | |
cb10cded | 283 | |
d852c6bc S |
284 | formats = [] |
285 | format_urls = set() | |
286 | ||
287 | sources = self._parse_json( | |
288 | self._search_regex( | |
289 | r'sources\s*:\s*({.+?})\s*,?\s*\n', webpage, 'sources', | |
290 | default='{}'), | |
291 | video_id, fatal=False) | |
292 | for format_id, format_url in sources.items(): | |
3052a30d S |
293 | format_url = url_or_none(format_url) |
294 | if not format_url: | |
d852c6bc S |
295 | continue |
296 | if format_url in format_urls: | |
297 | continue | |
298 | format_urls.add(format_url) | |
299 | formats.append({ | |
300 | 'format_id': format_id, | |
301 | 'url': format_url, | |
fea92aa6 | 302 | 'height': get_height(format_id), |
d852c6bc S |
303 | }) |
304 | ||
305 | video_url = self._search_regex( | |
306 | [r'''file\s*:\s*(?P<q>["'])(?P<mp4>.+?)(?P=q)''', | |
307 | r'''<a\s+href=(?P<q>["'])(?P<mp4>.+?)(?P=q)\s+class=["']mp4Thumb''', | |
308 | r'''<video[^>]+file=(?P<q>["'])(?P<mp4>.+?)(?P=q)[^>]*>'''], | |
309 | webpage, 'video url', group='mp4', default=None) | |
310 | if video_url and video_url not in format_urls: | |
311 | formats.append({ | |
312 | 'url': video_url, | |
313 | }) | |
314 | ||
4353cf51 | 315 | # Only a few videos have an description |
22ff1c4a | 316 | mobj = re.search(r'<span>Description: </span>([^<]+)', webpage) |
ccb079ee | 317 | description = mobj.group(1) if mobj else None |
cb10cded | 318 | |
4763b624 S |
319 | upload_date = unified_strdate(self._search_regex( |
320 | r'hint=["\'](\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2} [A-Z]{3,4}', | |
321 | webpage, 'upload date', fatal=False)) | |
cb10cded | 322 | |
3e485224 | 323 | uploader = self._html_search_regex( |
a846173d | 324 | r'<span[^>]+itemprop=["\']author[^>]+><a[^>]+><span[^>]+>([^<]+)', |
3e485224 | 325 | webpage, 'uploader', default='anonymous') |
cb10cded | 326 | |
251a44b7 | 327 | thumbnail = self._search_regex( |
b271e335 W |
328 | [r'''["']thumbUrl["']\s*:\s*(?P<q>["'])(?P<thumbnail>.+?)(?P=q)''', |
329 | r'''<video[^>]+"poster"=(?P<q>["'])(?P<thumbnail>.+?)(?P=q)[^>]*>'''], | |
c73cdd80 | 330 | webpage, 'thumbnail', fatal=False, group='thumbnail') |
ccb079ee | 331 | |
51378d35 | 332 | duration = parse_duration(self._search_regex( |
d852c6bc S |
333 | [r'<[^<]+\bitemprop=["\']duration["\'][^<]+\bcontent=["\'](.+?)["\']', |
334 | r'Runtime:\s*</span>\s*([\d:]+)'], webpage, | |
51378d35 | 335 | 'duration', fatal=False)) |
ccb079ee | 336 | |
6a16fd4a S |
337 | view_count = int_or_none(self._search_regex( |
338 | r'content=["\']User(?:View|Play)s:(\d+)', | |
339 | webpage, 'view count', fatal=False)) | |
ccb079ee | 340 | |
a846173d | 341 | mobj = re.search(r'hint=[\'"](?P<likecount>\d+) Likes / (?P<dislikecount>\d+) Dislikes', webpage) |
ccb079ee S |
342 | (like_count, dislike_count) = (mobj.group('likecount'), mobj.group('dislikecount')) if mobj else (None, None) |
343 | ||
344 | mobj = re.search(r'</label>Comments \((?P<commentcount>\d+)\)</div>', webpage) | |
345 | comment_count = mobj.group('commentcount') if mobj else 0 | |
cb10cded | 346 | |
85552042 S |
347 | categories_html = self._search_regex( |
348 | r'(?s)<table.+?(<span>Categories:.+?)</table>', webpage, | |
349 | 'categories', default=None) | |
350 | categories = [clean_html(category) for category in re.findall( | |
351 | r'<a[^>]+>(.+?)</a>', categories_html)] if categories_html else None | |
352 | ||
5d0c9754 | 353 | return { |
354 | 'id': video_id, | |
d852c6bc | 355 | 'display_id': display_id, |
ccb079ee S |
356 | 'title': title, |
357 | 'description': description, | |
358 | 'upload_date': upload_date, | |
3e485224 | 359 | 'uploader': uploader, |
908b56ea | 360 | 'uploader_id': uploader.lower() if uploader else None, |
ccb079ee S |
361 | 'thumbnail': thumbnail, |
362 | 'duration': duration, | |
363 | 'view_count': view_count, | |
364 | 'like_count': int_or_none(like_count), | |
365 | 'dislike_count': int_or_none(dislike_count), | |
366 | 'comment_count': int_or_none(comment_count), | |
9d92015d | 367 | 'age_limit': age_limit, |
85552042 | 368 | 'categories': categories, |
ccb079ee | 369 | 'formats': formats, |
5d0c9754 | 370 | } |
0bbba43e S |
371 | |
372 | ||
373 | class XHamsterEmbedIE(InfoExtractor): | |
add96eb9 | 374 | _VALID_URL = rf'https?://(?:[^/?#]+\.)?{XHamsterIE._DOMAINS}/xembed\.php\?video=(?P<id>\d+)' |
bfd973ec | 375 | _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?xhamster\.com/xembed\.php\?video=\d+)\1'] |
0bbba43e S |
376 | _TEST = { |
377 | 'url': 'http://xhamster.com/xembed.php?video=3328539', | |
378 | 'info_dict': { | |
379 | 'id': '3328539', | |
380 | 'ext': 'mp4', | |
381 | 'title': 'Pen Masturbation', | |
b271e335 | 382 | 'timestamp': 1406581861, |
0bbba43e | 383 | 'upload_date': '20140728', |
b271e335 | 384 | 'uploader': 'ManyakisArt', |
0bbba43e S |
385 | 'duration': 5, |
386 | 'age_limit': 18, | |
add96eb9 | 387 | }, |
0bbba43e S |
388 | } |
389 | ||
390 | def _real_extract(self, url): | |
391 | video_id = self._match_id(url) | |
392 | ||
393 | webpage = self._download_webpage(url, video_id) | |
394 | ||
395 | video_url = self._search_regex( | |
add96eb9 | 396 | rf'href="(https?://xhamster\.com/(?:movies/{video_id}/[^"]*\.html|videos/[^/]*-{video_id})[^"]*)"', |
065c4b27 S |
397 | webpage, 'xhamster url', default=None) |
398 | ||
399 | if not video_url: | |
add96eb9 | 400 | player_vars = self._parse_json( |
065c4b27 S |
401 | self._search_regex(r'vars\s*:\s*({.+?})\s*,\s*\n', webpage, 'vars'), |
402 | video_id) | |
add96eb9 | 403 | video_url = dict_get(player_vars, ('downloadLink', 'homepageLink', 'commentsLink', 'shareUrl')) |
0bbba43e | 404 | |
25701d5a | 405 | return self.url_result(video_url, 'XHamster') |
df228355 S |
406 | |
407 | ||
408 | class XHamsterUserIE(InfoExtractor): | |
cc8d8441 | 409 | _VALID_URL = rf'https?://(?:[^/?#]+\.)?{XHamsterIE._DOMAINS}/(?:(?P<user>users)|creators)/(?P<id>[^/?#&]+)' |
df228355 S |
410 | _TESTS = [{ |
411 | # Paginated user profile | |
412 | 'url': 'https://xhamster.com/users/netvideogirls/videos', | |
413 | 'info_dict': { | |
414 | 'id': 'netvideogirls', | |
415 | }, | |
416 | 'playlist_mincount': 267, | |
417 | }, { | |
418 | # Non-paginated user profile | |
419 | 'url': 'https://xhamster.com/users/firatkaan/videos', | |
420 | 'info_dict': { | |
421 | 'id': 'firatkaan', | |
422 | }, | |
423 | 'playlist_mincount': 1, | |
cc8d8441 SS |
424 | }, { |
425 | 'url': 'https://xhamster.com/creators/squirt-orgasm-69', | |
426 | 'info_dict': { | |
427 | 'id': 'squirt-orgasm-69', | |
428 | }, | |
429 | 'playlist_mincount': 150, | |
6d1b3489 | 430 | }, { |
431 | 'url': 'https://xhday.com/users/mobhunter', | |
432 | 'only_matching': True, | |
45b2ee6f | 433 | }, { |
434 | 'url': 'https://xhvid.com/users/pelushe21', | |
435 | 'only_matching': True, | |
df228355 S |
436 | }] |
437 | ||
cc8d8441 SS |
438 | def _entries(self, user_id, is_user): |
439 | prefix, suffix = ('users', 'videos') if is_user else ('creators', 'exclusive') | |
440 | next_page_url = f'https://xhamster.com/{prefix}/{user_id}/{suffix}/1' | |
df228355 S |
441 | for pagenum in itertools.count(1): |
442 | page = self._download_webpage( | |
add96eb9 | 443 | next_page_url, user_id, f'Downloading page {pagenum}') |
df228355 S |
444 | for video_tag in re.findall( |
445 | r'(<a[^>]+class=["\'].*?\bvideo-thumb__image-container[^>]+>)', | |
446 | page): | |
447 | video = extract_attributes(video_tag) | |
448 | video_url = url_or_none(video.get('href')) | |
449 | if not video_url or not XHamsterIE.suitable(video_url): | |
450 | continue | |
451 | video_id = XHamsterIE._match_id(video_url) | |
452 | yield self.url_result( | |
453 | video_url, ie=XHamsterIE.ie_key(), video_id=video_id) | |
454 | mobj = re.search(r'<a[^>]+data-page=["\']next[^>]+>', page) | |
455 | if not mobj: | |
456 | break | |
457 | next_page = extract_attributes(mobj.group(0)) | |
458 | next_page_url = url_or_none(next_page.get('href')) | |
459 | if not next_page_url: | |
460 | break | |
461 | ||
462 | def _real_extract(self, url): | |
cc8d8441 SS |
463 | user, user_id = self._match_valid_url(url).group('user', 'id') |
464 | return self.playlist_result(self._entries(user_id, bool(user)), user_id) |