3 from .common
import InfoExtractor
4 from ..compat
import compat_parse_qs
14 class RedGifsBaseInfoExtractor(InfoExtractor
):
22 'referer': 'https://www.redgifs.com/',
23 'origin': 'https://www.redgifs.com',
24 'content-type': 'application/json',
27 def _parse_gif_data(self
, gif_data
):
28 video_id
= gif_data
.get('id')
29 quality
= qualities(tuple(self
._FORMATS
.keys()))
31 orig_height
= int_or_none(gif_data
.get('height'))
32 aspect_ratio
= try_get(gif_data
, lambda x
: orig_height
/ x
['width'])
35 for format_id
, height
in self
._FORMATS
.items():
36 video_url
= gif_data
['urls'].get(format_id
)
39 height
= min(orig_height
, height
or orig_height
)
42 'format_id': format_id
,
43 'width': height
* aspect_ratio
if aspect_ratio
else None,
45 'quality': quality(format_id
),
47 self
._sort
_formats
(formats
)
51 'webpage_url': f
'https://redgifs.com/watch/{video_id}',
52 'extractor_key': RedGifsIE
.ie_key(),
53 'extractor': 'RedGifs',
54 'title': ' '.join(gif_data
.get('tags') or []) or 'RedGifs',
55 'timestamp': int_or_none(gif_data
.get('createDate')),
56 'uploader': gif_data
.get('userName'),
57 'duration': int_or_none(gif_data
.get('duration')),
58 'view_count': int_or_none(gif_data
.get('views')),
59 'like_count': int_or_none(gif_data
.get('likes')),
60 'categories': gif_data
.get('tags') or [],
61 'tags': gif_data
.get('tags'),
66 def _fetch_oauth_token(self
, video_id
):
67 # These pages contain the OAuth token that is necessary to make API calls.
68 index_page
= self
._download
_webpage
(f
'https://www.redgifs.com/watch/{video_id}', video_id
)
69 index_js_uri
= self
._html
_search
_regex
(
70 r
'href="?(/assets/js/index[.a-z0-9]*.js)"?\W', index_page
, 'index_js_uri')
71 index_js
= self
._download
_webpage
(f
'https://www.redgifs.com/{index_js_uri}', video_id
)
72 # It turns out that a { followed by any valid JSON punctuation will always result in the
73 # first two characters of the base64 encoding being "ey".
74 # Use this fact to find any such string constant of a reasonable length with the correct
75 # punctuation for an oauth token
76 oauth_token
= self
._html
_search
_regex
(
77 r
'\w+\s*[=:]\s*"(ey[^"]+\.[^"]*\.[^"]{43,45})"', index_js
, 'oauth token')
78 self
._API
_HEADERS
['authorization'] = f
'Bearer {oauth_token}'
80 def _call_api(self
, ep
, video_id
, *args
, **kwargs
):
81 if 'authorization' not in self
._API
_HEADERS
:
82 self
._fetch
_oauth
_token
(video_id
)
83 assert 'authorization' in self
._API
_HEADERS
85 headers
= dict(self
._API
_HEADERS
)
86 headers
['x-customheader'] = f
'https://www.redgifs.com/watch/{video_id}'
87 data
= self
._download
_json
(
88 f
'https://api.redgifs.com/v2/{ep}', video_id
, headers
=headers
, *args
, **kwargs
)
90 raise ExtractorError(f
'RedGifs said: {data["error"]}', expected
=True, video_id
=video_id
)
93 def _fetch_page(self
, ep
, video_id
, query
, page
):
94 query
['page'] = page
+ 1
95 data
= self
._call
_api
(
96 ep
, video_id
, query
=query
, note
=f
'Downloading JSON metadata page {page + 1}')
98 for entry
in data
['gifs']:
99 yield self
._parse
_gif
_data
(entry
)
101 def _prepare_api_query(self
, query
, fields
):
103 (field_name
, query
.get(field_name
, (default
,))[0])
104 for field_name
, default
in fields
.items()]
106 return {key: val for key, val in api_query if val is not None}
108 def _paged_entries(self
, ep
, item_id
, query
, fields
):
109 page
= int_or_none(query
.get('page', (None,))[0])
110 page_fetcher
= functools
.partial(
111 self
._fetch
_page
, ep
, item_id
, self
._prepare
_api
_query
(query
, fields
))
112 return page_fetcher(page
) if page
else OnDemandPagedList(page_fetcher
, self
._PAGE
_SIZE
)
115 class RedGifsIE(RedGifsBaseInfoExtractor
):
116 _VALID_URL
= r
'https?://(?:(?:www\.)?redgifs\.com/watch/|thumbs2\.redgifs\.com/)(?P<id>[^-/?#\.]+)'
118 'url': 'https://www.redgifs.com/watch/squeakyhelplesswisent',
120 'id': 'squeakyhelplesswisent',
122 'title': 'Hotwife Legs Thick',
123 'timestamp': 1636287915,
124 'upload_date': '20211107',
125 'uploader': 'ignored52',
134 'url': 'https://thumbs2.redgifs.com/SqueakyHelplessWisent-mobile.mp4#t=0',
136 'id': 'squeakyhelplesswisent',
138 'title': 'Hotwife Legs Thick',
139 'timestamp': 1636287915,
140 'upload_date': '20211107',
141 'uploader': 'ignored52',
151 def _real_extract(self
, url
):
152 video_id
= self
._match
_id
(url
).lower()
153 video_info
= self
._call
_api
(
154 f
'gifs/{video_id}?views=yes', video_id
, note
='Downloading video info')
155 return self
._parse
_gif
_data
(video_info
['gif'])
158 class RedGifsSearchIE(RedGifsBaseInfoExtractor
):
159 IE_DESC
= 'Redgifs search'
160 _VALID_URL
= r
'https?://(?:www\.)?redgifs\.com/browse\?(?P<query>[^#]+)'
164 'url': 'https://www.redgifs.com/browse?tags=Lesbian',
166 'id': 'tags=Lesbian',
168 'description': 'RedGifs search for Lesbian, ordered by trending'
170 'playlist_mincount': 100,
173 'url': 'https://www.redgifs.com/browse?type=g&order=latest&tags=Lesbian',
175 'id': 'type=g&order=latest&tags=Lesbian',
177 'description': 'RedGifs search for Lesbian, ordered by latest'
179 'playlist_mincount': 100,
182 'url': 'https://www.redgifs.com/browse?type=g&order=latest&tags=Lesbian&page=2',
184 'id': 'type=g&order=latest&tags=Lesbian&page=2',
186 'description': 'RedGifs search for Lesbian, ordered by latest'
188 'playlist_count': 80,
192 def _real_extract(self
, url
):
193 query_str
= self
._match
_valid
_url
(url
).group('query')
194 query
= compat_parse_qs(query_str
)
195 if not query
.get('tags'):
196 raise ExtractorError('Invalid query tags', expected
=True)
198 tags
= query
.get('tags')[0]
199 order
= query
.get('order', ('trending',))[0]
201 query
['search_text'] = [tags
]
202 entries
= self
._paged
_entries
('gifs/search', query_str
, query
, {
208 return self
.playlist_result(
209 entries
, query_str
, tags
, f
'RedGifs search for {tags}, ordered by {order}')
212 class RedGifsUserIE(RedGifsBaseInfoExtractor
):
213 IE_DESC
= 'Redgifs user'
214 _VALID_URL
= r
'https?://(?:www\.)?redgifs\.com/users/(?P<username>[^/?#]+)(?:\?(?P<query>[^#]+))?'
218 'url': 'https://www.redgifs.com/users/lamsinka89',
221 'title': 'lamsinka89',
222 'description': 'RedGifs user lamsinka89, ordered by recent'
224 'playlist_mincount': 100,
227 'url': 'https://www.redgifs.com/users/lamsinka89?page=3',
229 'id': 'lamsinka89?page=3',
230 'title': 'lamsinka89',
231 'description': 'RedGifs user lamsinka89, ordered by recent'
233 'playlist_count': 30,
236 'url': 'https://www.redgifs.com/users/lamsinka89?order=best&type=g',
238 'id': 'lamsinka89?order=best&type=g',
239 'title': 'lamsinka89',
240 'description': 'RedGifs user lamsinka89, ordered by best'
242 'playlist_mincount': 100,
246 def _real_extract(self
, url
):
247 username
, query_str
= self
._match
_valid
_url
(url
).group('username', 'query')
248 playlist_id
= f
'{username}?{query_str}' if query_str
else username
250 query
= compat_parse_qs(query_str
)
251 order
= query
.get('order', ('recent',))[0]
253 entries
= self
._paged
_entries
(f
'users/{username}/search', playlist_id
, query
, {
258 return self
.playlist_result(
259 entries
, playlist_id
, username
, f
'RedGifs user {username}, ordered by {order}')