4 from .common
import InfoExtractor
14 class MurrtubeIE(InfoExtractor
):
18 https?://murrtube\.net/videos/(?P<slug>[a-z0-9\-]+)\-
20 (?P<id>[a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{12})
23 'url': 'https://murrtube.net/videos/inferno-x-skyler-148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
24 'md5': '169f494812d9a90914b42978e73aa690',
26 'id': '148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
28 'title': 'Inferno X Skyler',
29 'description': 'Humping a very good slutty sheppy (roomate)',
30 'thumbnail': r
're:^https?://.*\.jpg$',
32 'uploader': 'Inferno Wolf',
37 'tags': ['hump', 'breed', 'Fursuit', 'murrsuit', 'bareback'],
41 def _download_gql(self
, video_id
, op
, note
=None, fatal
=True):
42 result
= self
._download
_json
(
43 'https://murrtube.net/graphql',
44 video_id
, note
, data
=json
.dumps(op
).encode(), fatal
=fatal
,
45 headers
={'Content-Type': 'application/json'}
)
48 def _real_extract(self
, url
):
49 video_id
= self
._match
_id
(url
)
50 data
= self
._download
_gql
(video_id
, {
51 'operationName': 'Medium',
56 query Medium($id: ID!) {
76 storage_url
= 'https://storage.murrtube.net/murrtube/'
77 format_url
= storage_url
+ meta
.get('key', '')
78 thumbnail
= storage_url
+ meta
.get('thumbnailKey', '')
80 if determine_ext(format_url
) == 'm3u8':
81 formats
= self
._extract
_m
3u8_formats
(
82 format_url
, video_id
, 'mp4', entry_protocol
='m3u8_native', fatal
=False)
84 formats
= [{'url': format_url}
]
88 'title': meta
.get('title'),
89 'description': meta
.get('description'),
91 'thumbnail': thumbnail
,
92 'duration': int_or_none(meta
.get('duration')),
93 'uploader': try_get(meta
, lambda x
: x
['user']['name']),
94 'view_count': meta
.get('viewsCount'),
95 'like_count': meta
.get('likesCount'),
96 'comment_count': meta
.get('commentsCount'),
97 'tags': meta
.get('tagList'),
102 class MurrtubeUserIE(MurrtubeIE
): # XXX: Do not subclass from concrete IE
103 IE_DESC
= 'Murrtube user profile'
104 _VALID_URL
= r
'https?://murrtube\.net/(?P<id>[^/]+)$'
106 'url': 'https://murrtube.net/stormy',
110 'playlist_mincount': 27,
114 def _fetch_page(self
, username
, user_id
, page
):
115 data
= self
._download
_gql
(username
, {
116 'operationName': 'Media',
118 'limit': self
._PAGE
_SIZE
,
119 'offset': page
* self
._PAGE
_SIZE
,
124 query Media($q: String, $sort: String, $userId: ID, $offset: Int!, $limit: Int!) {
125 media(q: $q, sort: $sort, userId: $userId, offset: $offset, limit: $limit) {
130 'Downloading page {0}'.format(page
+ 1))
132 raise ExtractorError(f
'Failed to retrieve video list for page {page + 1}')
134 media
= data
['media']
137 yield self
.url_result('murrtube:{0}'.format(entry
['id']), MurrtubeIE
.ie_key())
139 def _real_extract(self
, url
):
140 username
= self
._match
_id
(url
)
141 data
= self
._download
_gql
(username
, {
142 'operationName': 'User',
147 query User($id: ID!) {
153 'Downloading user info')
155 raise ExtractorError('Failed to fetch user info')
159 entries
= OnDemandPagedList(functools
.partial(
160 self
._fetch
_page
, username
, user
.get('id')), self
._PAGE
_SIZE
)
162 return self
.playlist_result(entries
, username
)