1 from .common
import InfoExtractor
2 from ..networking
.exceptions
import HTTPError
10 class PixivSketchBaseIE(InfoExtractor
):
11 def _call_api(self
, video_id
, path
, referer
, note
='Downloading JSON metadata'):
12 response
= self
._download
_json
(f
'https://sketch.pixiv.net/api/{path}', video_id
, note
=note
, headers
={
14 'X-Requested-With': referer
,
16 errors
= traverse_obj(response
, ('errors', ..., 'message'))
18 raise ExtractorError(' '.join(f
'{e}.' for e
in errors
))
19 return response
.get('data') or {}
22 class PixivSketchIE(PixivSketchBaseIE
):
23 IE_NAME
= 'pixiv:sketch'
24 _VALID_URL
= r
'https?://sketch\.pixiv\.net/@(?P<uploader_id>[a-zA-Z0-9_-]+)/lives/(?P<id>\d+)/?'
26 'url': 'https://sketch.pixiv.net/@nuhutya/lives/3654620468641830507',
28 'id': '7370666691623196569',
29 'title': 'まにあえクリスマス!',
31 'uploader_id': 'nuhutya',
32 'channel_id': '9844815',
34 'timestamp': 1640351536,
38 # these two (age_limit > 0) requires you to login on website, but it's actually not required for download
39 'url': 'https://sketch.pixiv.net/@namahyou/lives/4393103321546851377',
41 'id': '4907995960957946943',
42 'title': 'クリスマスなんて知らん🖕',
44 'uploader_id': 'suya2mori2',
45 'channel_id': '31169300',
47 'timestamp': 1640347640,
51 'url': 'https://sketch.pixiv.net/@8aki/lives/3553803162487249670',
53 'id': '1593420639479156945',
54 'title': 'おまけ本作業(リョナ有)',
55 'uploader': 'おぶい / Obui',
56 'uploader_id': 'oving',
57 'channel_id': '17606',
59 'timestamp': 1640330263,
64 def _real_extract(self
, url
):
65 video_id
, uploader_id
= self
._match
_valid
_url
(url
).group('id', 'uploader_id')
66 data
= self
._call
_api
(video_id
, f
'lives/{video_id}.json', url
)
68 if not traverse_obj(data
, 'is_broadcasting'):
69 raise ExtractorError(f
'This live is offline. Use https://sketch.pixiv.net/@{uploader_id} for ongoing live.', expected
=True)
71 m3u8_url
= traverse_obj(data
, ('owner', 'hls_movie', 'url'))
72 formats
= self
._extract
_m
3u8_formats
(
73 m3u8_url
, video_id
, ext
='mp4',
74 entry_protocol
='m3u8_native', m3u8_id
='hls')
78 'title': data
.get('name'),
80 'uploader': traverse_obj(data
, ('user', 'name'), ('owner', 'user', 'name')),
81 'uploader_id': traverse_obj(data
, ('user', 'unique_name'), ('owner', 'user', 'unique_name')),
82 'channel_id': str(traverse_obj(data
, ('user', 'pixiv_user_id'), ('owner', 'user', 'pixiv_user_id'))),
83 'age_limit': 18 if data
.get('is_r18') else 15 if data
.get('is_r15') else 0,
84 'timestamp': unified_timestamp(data
.get('created_at')),
89 class PixivSketchUserIE(PixivSketchBaseIE
):
90 IE_NAME
= 'pixiv:sketch:user'
91 _VALID_URL
= r
'https?://sketch\.pixiv\.net/@(?P<id>[a-zA-Z0-9_-]+)/?'
93 'url': 'https://sketch.pixiv.net/@nuhutya',
94 'only_matching': True,
96 'url': 'https://sketch.pixiv.net/@namahyou',
97 'only_matching': True,
99 'url': 'https://sketch.pixiv.net/@8aki',
100 'only_matching': True,
104 def suitable(cls
, url
):
105 return super().suitable(url
) and not PixivSketchIE
.suitable(url
)
107 def _real_extract(self
, url
):
108 user_id
= self
._match
_id
(url
)
109 data
= self
._call
_api
(user_id
, f
'lives/users/@{user_id}.json', url
)
111 if not traverse_obj(data
, 'is_broadcasting'):
113 self
._call
_api
(user_id
, 'users/current.json', url
, 'Investigating reason for request failure')
114 except ExtractorError
as e
:
115 if isinstance(e
.cause
, HTTPError
) and e
.cause
.status
== 401:
116 self
.raise_login_required(f
'Please log in, or use direct link like https://sketch.pixiv.net/@{user_id}/1234567890', method
='cookies')
117 raise ExtractorError('This user is offline', expected
=True)
119 return self
.url_result(f
'https://sketch.pixiv.net/@{user_id}/lives/{data["id"]}')