[pornhub] Fix thumbnail and duration extraction (Closes #8604)
[youtube-dl] / youtube_dl / extractor / pornhub.py
1 from __future__ import unicode_literals
2
3 import os
4 import re
5
6 from .common import InfoExtractor
7 from ..compat import (
8     compat_urllib_parse_unquote,
9     compat_urllib_parse_unquote_plus,
10     compat_urllib_parse_urlparse,
11 )
12 from ..utils import (
13     ExtractorError,
14     int_or_none,
15     sanitized_Request,
16     str_to_int,
17 )
18 from ..aes import (
19     aes_decrypt_text
20 )
21
22
23 class PornHubIE(InfoExtractor):
24     _VALID_URL = r'https?://(?:[a-z]+\.)?pornhub\.com/(?:view_video\.php\?viewkey=|embed/)(?P<id>[0-9a-z]+)'
25     _TESTS = [{
26         'url': 'http://www.pornhub.com/view_video.php?viewkey=648719015',
27         'md5': '1e19b41231a02eba417839222ac9d58e',
28         'info_dict': {
29             'id': '648719015',
30             'ext': 'mp4',
31             'title': 'Seductive Indian beauty strips down and fingers her pink pussy',
32             'uploader': 'Babes',
33             'duration': 361,
34             'view_count': int,
35             'like_count': int,
36             'dislike_count': int,
37             'comment_count': int,
38             'age_limit': 18,
39         }
40     }, {
41         'url': 'http://www.pornhub.com/view_video.php?viewkey=ph557bbb6676d2d',
42         'only_matching': True,
43     }, {
44         'url': 'http://fr.pornhub.com/view_video.php?viewkey=ph55ca2f9760862',
45         'only_matching': True,
46     }]
47
48     @classmethod
49     def _extract_url(cls, webpage):
50         mobj = re.search(
51             r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?pornhub\.com/embed/\d+)\1', webpage)
52         if mobj:
53             return mobj.group('url')
54
55     def _extract_count(self, pattern, webpage, name):
56         return str_to_int(self._search_regex(
57             pattern, webpage, '%s count' % name, fatal=False))
58
59     def _real_extract(self, url):
60         video_id = self._match_id(url)
61
62         req = sanitized_Request(
63             'http://www.pornhub.com/view_video.php?viewkey=%s' % video_id)
64         req.add_header('Cookie', 'age_verified=1')
65         webpage = self._download_webpage(req, video_id)
66
67         error_msg = self._html_search_regex(
68             r'(?s)<div class="userMessageSection[^"]*".*?>(.*?)</div>',
69             webpage, 'error message', default=None)
70         if error_msg:
71             error_msg = re.sub(r'\s+', ' ', error_msg)
72             raise ExtractorError(
73                 'PornHub said: %s' % error_msg,
74                 expected=True, video_id=video_id)
75
76         flashvars = self._parse_json(
77             self._search_regex(
78                 r'var\s+flashv1ars_\d+\s*=\s*({.+?});', webpage, 'flashvars', default='{}'),
79             video_id)
80         if flashvars:
81             video_title = flashvars.get('video_title')
82             thumbnail = flashvars.get('image_url')
83             duration = int_or_none(flashvars.get('video_duration'))
84         else:
85             video_title, thumbnail, duration = [None] * 3
86
87         if not video_title:
88             video_title = self._html_search_regex(r'<h1 [^>]+>([^<]+)', webpage, 'title')
89
90         video_uploader = self._html_search_regex(
91             r'(?s)From:&nbsp;.+?<(?:a href="/users/|a href="/channels/|span class="username)[^>]+>(.+?)<',
92             webpage, 'uploader', fatal=False)
93
94         view_count = self._extract_count(
95             r'<span class="count">([\d,\.]+)</span> views', webpage, 'view')
96         like_count = self._extract_count(
97             r'<span class="votesUp">([\d,\.]+)</span>', webpage, 'like')
98         dislike_count = self._extract_count(
99             r'<span class="votesDown">([\d,\.]+)</span>', webpage, 'dislike')
100         comment_count = self._extract_count(
101             r'All Comments\s*<span>\(([\d,.]+)\)', webpage, 'comment')
102
103         video_urls = list(map(compat_urllib_parse_unquote, re.findall(r"player_quality_[0-9]{3}p\s*=\s*'([^']+)'", webpage)))
104         if webpage.find('"encrypted":true') != -1:
105             password = compat_urllib_parse_unquote_plus(
106                 self._search_regex(r'"video_title":"([^"]+)', webpage, 'password'))
107             video_urls = list(map(lambda s: aes_decrypt_text(s, password, 32).decode('utf-8'), video_urls))
108
109         formats = []
110         for video_url in video_urls:
111             path = compat_urllib_parse_urlparse(video_url).path
112             extension = os.path.splitext(path)[1][1:]
113             format = path.split('/')[5].split('_')[:2]
114             format = '-'.join(format)
115
116             m = re.match(r'^(?P<height>[0-9]+)[pP]-(?P<tbr>[0-9]+)[kK]$', format)
117             if m is None:
118                 height = None
119                 tbr = None
120             else:
121                 height = int(m.group('height'))
122                 tbr = int(m.group('tbr'))
123
124             formats.append({
125                 'url': video_url,
126                 'ext': extension,
127                 'format': format,
128                 'format_id': format,
129                 'tbr': tbr,
130                 'height': height,
131             })
132         self._sort_formats(formats)
133
134         return {
135             'id': video_id,
136             'uploader': video_uploader,
137             'title': video_title,
138             'thumbnail': thumbnail,
139             'duration': duration,
140             'view_count': view_count,
141             'like_count': like_count,
142             'dislike_count': dislike_count,
143             'comment_count': comment_count,
144             'formats': formats,
145             'age_limit': 18,
146         }
147
148
149 class PornHubPlaylistBaseIE(InfoExtractor):
150     def _extract_entries(self, webpage):
151         return [
152             self.url_result('http://www.pornhub.com/%s' % video_url, PornHubIE.ie_key())
153             for video_url in set(re.findall(
154                 r'href="/?(view_video\.php\?.*\bviewkey=[\da-z]+[^"]*)"', webpage))
155         ]
156
157     def _real_extract(self, url):
158         playlist_id = self._match_id(url)
159
160         webpage = self._download_webpage(url, playlist_id)
161
162         entries = self._extract_entries(webpage)
163
164         playlist = self._parse_json(
165             self._search_regex(
166                 r'playlistObject\s*=\s*({.+?});', webpage, 'playlist'),
167             playlist_id)
168
169         return self.playlist_result(
170             entries, playlist_id, playlist.get('title'), playlist.get('description'))
171
172
173 class PornHubPlaylistIE(PornHubPlaylistBaseIE):
174     _VALID_URL = r'https?://(?:www\.)?pornhub\.com/playlist/(?P<id>\d+)'
175     _TESTS = [{
176         'url': 'http://www.pornhub.com/playlist/6201671',
177         'info_dict': {
178             'id': '6201671',
179             'title': 'P0p4',
180         },
181         'playlist_mincount': 35,
182     }]
183
184
185 class PornHubUserVideosIE(PornHubPlaylistBaseIE):
186     _VALID_URL = r'https?://(?:www\.)?pornhub\.com/users/(?P<id>[^/]+)/videos'
187     _TESTS = [{
188         'url': 'http://www.pornhub.com/users/rushandlia/videos',
189         'info_dict': {
190             'id': 'rushandlia',
191         },
192         'playlist_mincount': 13,
193     }]
194
195     def _real_extract(self, url):
196         user_id = self._match_id(url)
197
198         webpage = self._download_webpage(url, user_id)
199
200         return self.playlist_result(self._extract_entries(webpage), user_id)