2 from __future__
import unicode_literals
7 from .common
import InfoExtractor
16 class TrovoBaseIE(InfoExtractor
):
17 _VALID_URL_BASE
= r
'https?://(?:www\.)?trovo\.live/'
18 _HEADERS
= {'Origin': 'https://trovo.live'}
20 def _call_api(self
, video_id
, query
=None, data
=None):
21 return self
._download
_json
(
22 'https://gql.trovo.live/', video_id
, query
=query
, data
=data
,
23 headers
={'Accept': 'application/json'}
)
25 def _extract_streamer_info(self
, data
):
26 streamer_info
= data
.get('streamerInfo') or {}
27 username
= streamer_info
.get('userName')
29 'uploader': streamer_info
.get('nickName'),
30 'uploader_id': str_or_none(streamer_info
.get('uid')),
31 'uploader_url': 'https://trovo.live/' + username
if username
else None,
35 class TrovoIE(TrovoBaseIE
):
36 _VALID_URL
= TrovoBaseIE
._VALID
_URL
_BASE
+ r
'(?!(?:clip|video)/)(?P<id>[^/?&#]+)'
38 def _real_extract(self
, url
):
39 username
= self
._match
_id
(url
)
40 live_info
= self
._call
_api
(username
, query
={
42 getLiveInfo(params: {userName: "%s"}) {
60 })['data']['getLiveInfo']
61 if live_info
.get('isLive') == 0:
62 raise ExtractorError('%s is offline' % username
, expected
=True)
63 program_info
= live_info
['programInfo']
64 program_id
= program_info
['id']
65 title
= program_info
['title']
68 for stream_info
in (program_info
.get('streamInfo') or []):
69 play_url
= stream_info
.get('playUrl')
72 format_id
= stream_info
.get('desc')
74 'format_id': format_id
,
75 'height': int_or_none(format_id
[:-1]) if format_id
else None,
77 'http_headers': self
._HEADERS
,
79 self
._sort
_formats
(formats
)
85 'thumbnail': program_info
.get('coverUrl'),
88 info
.update(self
._extract
_streamer
_info
(live_info
))
92 class TrovoVodIE(TrovoBaseIE
):
93 _VALID_URL
= TrovoBaseIE
._VALID
_URL
_BASE
+ r
'(?:clip|video)/(?P<id>[^/?&#]+)'
95 'url': 'https://trovo.live/video/ltv-100095501_100095501_1609596043',
97 'id': 'ltv-100095501_100095501_1609596043',
99 'title': 'Spontaner 12 Stunden Stream! - Ok Boomer!',
101 'timestamp': 1609640305,
102 'upload_date': '20210103',
103 'uploader_id': '100095501',
107 'comment_count': int,
108 'comments': 'mincount:8',
109 'categories': ['Grand Theft Auto V'],
113 'url': 'https://trovo.live/clip/lc-5285890810184026005',
114 'only_matching': True,
117 def _real_extract(self
, url
):
118 vid
= self
._match
_id
(url
)
119 resp
= self
._call
_api
(vid
, data
=json
.dumps([{
121 batchGetVodDetailInfo(params: {vids: ["%s"]}) {
127 getCommentList(params: {appInfo: {postID: "%s"}, pageSize: 1000000000, preview: {}}) {
141 vod_detail_info
= resp
[0]['data']['batchGetVodDetailInfo']['VodDetailInfos'][vid
]
142 vod_info
= vod_detail_info
['vodInfo']
143 title
= vod_info
['title']
145 language
= vod_info
.get('languageName')
147 for play_info
in (vod_info
.get('playInfos') or []):
148 play_url
= play_info
.get('playUrl')
151 format_id
= play_info
.get('desc')
154 'filesize': int_or_none(play_info
.get('fileSize')),
155 'format_id': format_id
,
156 'height': int_or_none(format_id
[:-1]) if format_id
else None,
157 'language': language
,
158 'protocol': 'm3u8_native',
159 'tbr': int_or_none(play_info
.get('bitrate')),
161 'http_headers': self
._HEADERS
,
163 self
._sort
_formats
(formats
)
165 category
= vod_info
.get('categoryName')
166 get_count
= lambda x
: int_or_none(vod_info
.get(x
+ 'Num'))
168 comment_list
= try_get(resp
, lambda x
: x
[1]['data']['getCommentList']['commentList'], list) or []
170 for comment
in comment_list
:
171 content
= comment
.get('content')
174 author
= comment
.get('author') or {}
175 parent
= comment
.get('parentID')
177 'author': author
.get('nickName'),
178 'author_id': str_or_none(author
.get('uid')),
179 'id': str_or_none(comment
.get('commentID')),
181 'timestamp': int_or_none(comment
.get('createdAt')),
182 'parent': 'root' if parent
== 0 else str_or_none(parent
),
189 'thumbnail': vod_info
.get('coverUrl'),
190 'timestamp': int_or_none(vod_info
.get('publishTs')),
191 'duration': int_or_none(vod_info
.get('duration')),
192 'view_count': get_count('watch'),
193 'like_count': get_count('like'),
194 'comment_count': get_count('comment'),
195 'comments': comments
,
196 'categories': [category
] if category
else None,
198 info
.update(self
._extract
_streamer
_info
(vod_detail_info
))
202 class TrovoChannelBaseIE(TrovoBaseIE
):
203 def _get_vod_json(self
, page
, uid
):
204 raise NotImplementedError('This method must be implemented by subclasses')
206 def _entries(self
, uid
):
207 for page
in itertools
.count(1):
208 vod_json
= self
._get
_vod
_json
(page
, uid
)
209 vods
= vod_json
.get('vodInfos', [])
211 yield self
.url_result(
212 'https://trovo.live/%s/%s' % (self
._TYPE
, vod
.get('vid')),
213 ie
=TrovoVodIE
.ie_key())
214 has_more
= vod_json
['hasMore']
218 def _real_extract(self
, url
):
219 id = self
._match
_id
(url
)
220 uid
= str(self
._call
_api
(id, query
={
221 'query': '{getLiveInfo(params:{userName:"%s"}){streamerInfo{uid}}}' % id
222 })['data']['getLiveInfo']['streamerInfo']['uid'])
223 return self
.playlist_result(self
._entries
(uid
), playlist_id
=uid
)
226 class TrovoChannelVodIE(TrovoChannelBaseIE
):
227 _VALID_URL
= r
'trovovod:(?P<id>[^\s]+)'
228 IE_DESC
= 'All VODs of a trovo.live channel; "trovovod:" prefix'
231 'url': 'trovovod:OneTappedYou',
232 'playlist_mincount': 24,
238 _QUERY
= '{getChannelLtvVideoInfos(params:{pageSize:99,currPage:%d,channelID:%s}){hasMore,vodInfos{vid}}}'
241 def _get_vod_json(self
, page
, uid
):
242 return self
._call
_api
(uid
, query
={
243 'query': self
._QUERY
% (page
, uid
)
244 })['data']['getChannelLtvVideoInfos']
247 class TrovoChannelClipIE(TrovoChannelBaseIE
):
248 _VALID_URL
= r
'trovoclip:(?P<id>[^\s]+)'
249 IE_DESC
= 'All Clips of a trovo.live channel; "trovoclip:" prefix'
252 'url': 'trovoclip:OneTappedYou',
253 'playlist_mincount': 29,
259 _QUERY
= '{getChannelClipVideoInfos(params:{pageSize:99,currPage:%d,channelID:%s,albumType:VOD_CLIP_ALBUM_TYPE_LATEST}){hasMore,vodInfos{vid}}}'
262 def _get_vod_json(self
, page
, uid
):
263 return self
._call
_api
(uid
, query
={
264 'query': self
._QUERY
% (page
, uid
)
265 })['data']['getChannelClipVideoInfos']