[spankbang:playlist] Add extractor (closes #19145)
[youtube-dl] / youtube_dl / extractor / spankbang.py
1 from __future__ import unicode_literals
2
3 import re
4
5 from .common import InfoExtractor
6 from ..utils import (
7     ExtractorError,
8     orderedSet,
9     parse_duration,
10     parse_resolution,
11     str_to_int,
12 )
13
14
15 class SpankBangIE(InfoExtractor):
16     _VALID_URL = r'https?://(?:[^/]+\.)?spankbang\.com/(?P<id>[\da-z]+)/(?:video|play|embed)\b'
17     _TESTS = [{
18         'url': 'http://spankbang.com/3vvn/video/fantasy+solo',
19         'md5': '1cc433e1d6aa14bc376535b8679302f7',
20         'info_dict': {
21             'id': '3vvn',
22             'ext': 'mp4',
23             'title': 'fantasy solo',
24             'description': 'dillion harper masturbates on a bed',
25             'thumbnail': r're:^https?://.*\.jpg$',
26             'uploader': 'silly2587',
27             'age_limit': 18,
28         }
29     }, {
30         # 480p only
31         'url': 'http://spankbang.com/1vt0/video/solvane+gangbang',
32         'only_matching': True,
33     }, {
34         # no uploader
35         'url': 'http://spankbang.com/lklg/video/sex+with+anyone+wedding+edition+2',
36         'only_matching': True,
37     }, {
38         # mobile page
39         'url': 'http://m.spankbang.com/1o2de/video/can+t+remember+her+name',
40         'only_matching': True,
41     }, {
42         # 4k
43         'url': 'https://spankbang.com/1vwqx/video/jade+kush+solo+4k',
44         'only_matching': True,
45     }, {
46         'url': 'https://m.spankbang.com/3vvn/play/fantasy+solo/480p/',
47         'only_matching': True,
48     }, {
49         'url': 'https://m.spankbang.com/3vvn/play',
50         'only_matching': True,
51     }, {
52         'url': 'https://spankbang.com/2y3td/embed/',
53         'only_matching': True,
54     }]
55
56     def _real_extract(self, url):
57         video_id = self._match_id(url)
58         webpage = self._download_webpage(
59             url.replace('/%s/embed' % video_id, '/%s/video' % video_id),
60             video_id, headers={'Cookie': 'country=US'})
61
62         if re.search(r'<[^>]+\bid=["\']video_removed', webpage):
63             raise ExtractorError(
64                 'Video %s is not available' % video_id, expected=True)
65
66         formats = []
67         for mobj in re.finditer(
68                 r'stream_url_(?P<id>[^\s=]+)\s*=\s*(["\'])(?P<url>(?:(?!\2).)+)\2',
69                 webpage):
70             format_id, format_url = mobj.group('id', 'url')
71             f = parse_resolution(format_id)
72             f.update({
73                 'url': format_url,
74                 'format_id': format_id,
75             })
76             formats.append(f)
77         self._sort_formats(formats)
78
79         title = self._html_search_regex(
80             r'(?s)<h1[^>]*>(.+?)</h1>', webpage, 'title')
81         description = self._search_regex(
82             r'<div[^>]+\bclass=["\']bottom[^>]+>\s*<p>[^<]*</p>\s*<p>([^<]+)',
83             webpage, 'description', fatal=False)
84         thumbnail = self._og_search_thumbnail(webpage)
85         uploader = self._search_regex(
86             r'class="user"[^>]*><img[^>]+>([^<]+)',
87             webpage, 'uploader', default=None)
88         duration = parse_duration(self._search_regex(
89             r'<div[^>]+\bclass=["\']right_side[^>]+>\s*<span>([^<]+)',
90             webpage, 'duration', fatal=False))
91         view_count = str_to_int(self._search_regex(
92             r'([\d,.]+)\s+plays', webpage, 'view count', fatal=False))
93
94         age_limit = self._rta_search(webpage)
95
96         return {
97             'id': video_id,
98             'title': title,
99             'description': description,
100             'thumbnail': thumbnail,
101             'uploader': uploader,
102             'duration': duration,
103             'view_count': view_count,
104             'formats': formats,
105             'age_limit': age_limit,
106         }
107
108
109 class SpankBangPlaylistIE(InfoExtractor):
110     _VALID_URL = r'https?://(?:[^/]+\.)?spankbang\.com/(?P<id>[\da-z]+)/playlist/[^/]+'
111     _TEST = {
112         'url': 'https://spankbang.com/ug0k/playlist/big+ass+titties',
113         'info_dict': {
114             'id': 'ug0k',
115             'title': 'Big Ass Titties',
116         },
117         'playlist_mincount': 50,
118     }
119
120     def _real_extract(self, url):
121         playlist_id = self._match_id(url)
122
123         webpage = self._download_webpage(
124             url, playlist_id, headers={'Cookie': 'country=US; mobile=on'})
125
126         entries = [self.url_result(
127             'https://spankbang.com/%s/video' % video_id,
128             ie=SpankBangIE.ie_key(), video_id=video_id)
129             for video_id in orderedSet(re.findall(
130                 r'<a[^>]+\bhref=["\']/?([\da-z]+)/play/', webpage))]
131
132         title = self._html_search_regex(
133             r'<h1>([^<]+)\s+playlist</h1>', webpage, 'playlist title',
134             fatal=False)
135
136         return self.playlist_result(entries, playlist_id, title)