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