]>
Commit | Line | Data |
---|---|---|
1 | from .common import InfoExtractor | |
2 | from ..utils import ( | |
3 | ExtractorError, | |
4 | traverse_obj, | |
5 | unified_timestamp, | |
6 | ) | |
7 | ||
8 | ||
9 | class PixivSketchBaseIE(InfoExtractor): | |
10 | def _call_api(self, video_id, path, referer, note='Downloading JSON metadata'): | |
11 | response = self._download_json(f'https://sketch.pixiv.net/api/{path}', video_id, note=note, headers={ | |
12 | 'Referer': referer, | |
13 | 'X-Requested-With': referer, | |
14 | }) | |
15 | errors = traverse_obj(response, ('errors', ..., 'message')) | |
16 | if errors: | |
17 | raise ExtractorError(' '.join(f'{e}.' for e in errors)) | |
18 | return response.get('data') or {} | |
19 | ||
20 | ||
21 | class PixivSketchIE(PixivSketchBaseIE): | |
22 | IE_NAME = 'pixiv:sketch' | |
23 | _VALID_URL = r'https?://sketch\.pixiv\.net/@(?P<uploader_id>[a-zA-Z0-9_-]+)/lives/(?P<id>\d+)/?' | |
24 | _TESTS = [{ | |
25 | 'url': 'https://sketch.pixiv.net/@nuhutya/lives/3654620468641830507', | |
26 | 'info_dict': { | |
27 | 'id': '7370666691623196569', | |
28 | 'title': 'まにあえクリスマス!', | |
29 | 'uploader': 'ぬふちゃ', | |
30 | 'uploader_id': 'nuhutya', | |
31 | 'channel_id': '9844815', | |
32 | 'age_limit': 0, | |
33 | 'timestamp': 1640351536, | |
34 | }, | |
35 | 'skip': True, | |
36 | }, { | |
37 | # these two (age_limit > 0) requires you to login on website, but it's actually not required for download | |
38 | 'url': 'https://sketch.pixiv.net/@namahyou/lives/4393103321546851377', | |
39 | 'info_dict': { | |
40 | 'id': '4907995960957946943', | |
41 | 'title': 'クリスマスなんて知らん🖕', | |
42 | 'uploader': 'すゃもり', | |
43 | 'uploader_id': 'suya2mori2', | |
44 | 'channel_id': '31169300', | |
45 | 'age_limit': 15, | |
46 | 'timestamp': 1640347640, | |
47 | }, | |
48 | 'skip': True, | |
49 | }, { | |
50 | 'url': 'https://sketch.pixiv.net/@8aki/lives/3553803162487249670', | |
51 | 'info_dict': { | |
52 | 'id': '1593420639479156945', | |
53 | 'title': 'おまけ本作業(リョナ有)', | |
54 | 'uploader': 'おぶい / Obui', | |
55 | 'uploader_id': 'oving', | |
56 | 'channel_id': '17606', | |
57 | 'age_limit': 18, | |
58 | 'timestamp': 1640330263, | |
59 | }, | |
60 | 'skip': True, | |
61 | }] | |
62 | ||
63 | def _real_extract(self, url): | |
64 | video_id, uploader_id = self._match_valid_url(url).group('id', 'uploader_id') | |
65 | data = self._call_api(video_id, f'lives/{video_id}.json', url) | |
66 | ||
67 | if not traverse_obj(data, 'is_broadcasting'): | |
68 | raise ExtractorError(f'This live is offline. Use https://sketch.pixiv.net/@{uploader_id} for ongoing live.', expected=True) | |
69 | ||
70 | m3u8_url = traverse_obj(data, ('owner', 'hls_movie', 'url')) | |
71 | formats = self._extract_m3u8_formats( | |
72 | m3u8_url, video_id, ext='mp4', | |
73 | entry_protocol='m3u8_native', m3u8_id='hls') | |
74 | ||
75 | return { | |
76 | 'id': video_id, | |
77 | 'title': data.get('name'), | |
78 | 'formats': formats, | |
79 | 'uploader': traverse_obj(data, ('user', 'name'), ('owner', 'user', 'name')), | |
80 | 'uploader_id': traverse_obj(data, ('user', 'unique_name'), ('owner', 'user', 'unique_name')), | |
81 | 'channel_id': str(traverse_obj(data, ('user', 'pixiv_user_id'), ('owner', 'user', 'pixiv_user_id'))), | |
82 | 'age_limit': 18 if data.get('is_r18') else 15 if data.get('is_r15') else 0, | |
83 | 'timestamp': unified_timestamp(data.get('created_at')), | |
84 | 'is_live': True, | |
85 | } | |
86 | ||
87 | ||
88 | class PixivSketchUserIE(PixivSketchBaseIE): | |
89 | IE_NAME = 'pixiv:sketch:user' | |
90 | _VALID_URL = r'https?://sketch\.pixiv\.net/@(?P<id>[a-zA-Z0-9_-]+)/?' | |
91 | _TESTS = [{ | |
92 | 'url': 'https://sketch.pixiv.net/@nuhutya', | |
93 | 'only_matching': True, | |
94 | }, { | |
95 | 'url': 'https://sketch.pixiv.net/@namahyou', | |
96 | 'only_matching': True, | |
97 | }, { | |
98 | 'url': 'https://sketch.pixiv.net/@8aki', | |
99 | 'only_matching': True, | |
100 | }] | |
101 | ||
102 | @classmethod | |
103 | def suitable(cls, url): | |
104 | return super().suitable(url) and not PixivSketchIE.suitable(url) | |
105 | ||
106 | def _real_extract(self, url): | |
107 | user_id = self._match_id(url) | |
108 | data = self._call_api(user_id, f'lives/users/@{user_id}.json', url) | |
109 | ||
110 | if not traverse_obj(data, 'is_broadcasting'): | |
111 | try: | |
112 | self._call_api(user_id, 'users/current.json', url, 'Investigating reason for request failure') | |
113 | except ExtractorError as ex: | |
114 | if ex.cause and ex.cause.code == 401: | |
115 | self.raise_login_required(f'Please log in, or use direct link like https://sketch.pixiv.net/@{user_id}/1234567890', method='cookies') | |
116 | raise ExtractorError('This user is offline', expected=True) | |
117 | ||
118 | return self.url_result(f'https://sketch.pixiv.net/@{user_id}/lives/{data["id"]}') |