]>
Commit | Line | Data |
---|---|---|
db974389 FL |
1 | import urllib.parse |
2 | ||
d6166a76 | 3 | from .common import InfoExtractor |
a42839e5 S |
4 | from ..utils import ( |
5 | ExtractorError, | |
db974389 FL |
6 | str_or_none, |
7 | traverse_obj, | |
a42839e5 | 8 | ) |
d6166a76 PG |
9 | |
10 | ||
11 | class PicartoIE(InfoExtractor): | |
cce889b9 | 12 | _VALID_URL = r'https?://(?:www.)?picarto\.tv/(?P<id>[a-zA-Z0-9]+)' |
d6166a76 PG |
13 | _TEST = { |
14 | 'url': 'https://picarto.tv/Setz', | |
15 | 'info_dict': { | |
16 | 'id': 'Setz', | |
17 | 'ext': 'mp4', | |
18 | 'title': 're:^Setz [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$', | |
19 | 'timestamp': int, | |
20 | 'is_live': True | |
21 | }, | |
a42839e5 | 22 | 'skip': 'Stream is offline', |
d6166a76 PG |
23 | } |
24 | ||
a42839e5 S |
25 | @classmethod |
26 | def suitable(cls, url): | |
27 | return False if PicartoVodIE.suitable(url) else super(PicartoIE, cls).suitable(url) | |
28 | ||
d6166a76 | 29 | def _real_extract(self, url): |
cce889b9 | 30 | channel_id = self._match_id(url) |
31 | ||
32 | data = self._download_json( | |
33 | 'https://ptvintern.picarto.tv/ptvapi', channel_id, query={ | |
34 | 'query': '''{ | |
35 | channel(name: "%s") { | |
36 | adult | |
37 | id | |
38 | online | |
39 | stream_name | |
40 | title | |
41 | } | |
42 | getLoadBalancerUrl(channel_name: "%s") { | |
43 | url | |
44 | } | |
45 | }''' % (channel_id, channel_id), | |
46 | })['data'] | |
47 | metadata = data['channel'] | |
48 | ||
49 | if metadata.get('online') == 0: | |
d6166a76 | 50 | raise ExtractorError('Stream is offline', expected=True) |
cce889b9 | 51 | title = metadata['title'] |
d6166a76 | 52 | |
a42839e5 | 53 | cdn_data = self._download_json( |
cce889b9 | 54 | data['getLoadBalancerUrl']['url'] + '/stream/json_' + metadata['stream_name'] + '.js', |
55 | channel_id, 'Downloading load balancing info') | |
a42839e5 | 56 | |
a42839e5 | 57 | formats = [] |
cce889b9 | 58 | for source in (cdn_data.get('source') or []): |
59 | source_url = source.get('url') | |
60 | if not source_url: | |
a42839e5 | 61 | continue |
cce889b9 | 62 | source_type = source.get('type') |
63 | if source_type == 'html5/application/vnd.apple.mpegurl': | |
64 | formats.extend(self._extract_m3u8_formats( | |
65 | source_url, channel_id, 'mp4', m3u8_id='hls', fatal=False)) | |
66 | elif source_type == 'html5/video/mp4': | |
67 | formats.append({ | |
68 | 'url': source_url, | |
69 | }) | |
d6166a76 | 70 | |
f17a24a6 | 71 | mature = metadata.get('adult') |
a42839e5 S |
72 | if mature is None: |
73 | age_limit = None | |
74 | else: | |
75 | age_limit = 18 if mature is True else 0 | |
76 | ||
d6166a76 PG |
77 | return { |
78 | 'id': channel_id, | |
39ca3b5c | 79 | 'title': title.strip(), |
d6166a76 | 80 | 'is_live': True, |
730c0d12 | 81 | 'channel': channel_id, |
cce889b9 | 82 | 'channel_id': metadata.get('id'), |
730c0d12 | 83 | 'channel_url': 'https://picarto.tv/%s' % channel_id, |
a42839e5 S |
84 | 'age_limit': age_limit, |
85 | 'formats': formats, | |
d6166a76 PG |
86 | } |
87 | ||
88 | ||
89 | class PicartoVodIE(InfoExtractor): | |
db974389 | 90 | _VALID_URL = r'https?://(?:www\.)?picarto\.tv/(?:videopopout|\w+/videos)/(?P<id>[^/?#&]+)' |
a42839e5 S |
91 | _TESTS = [{ |
92 | 'url': 'https://picarto.tv/videopopout/ArtofZod_2017.12.12.00.13.23.flv', | |
93 | 'md5': '3ab45ba4352c52ee841a28fb73f2d9ca', | |
d6166a76 | 94 | 'info_dict': { |
a42839e5 | 95 | 'id': 'ArtofZod_2017.12.12.00.13.23.flv', |
d6166a76 | 96 | 'ext': 'mp4', |
a42839e5 S |
97 | 'title': 'ArtofZod_2017.12.12.00.13.23.flv', |
98 | 'thumbnail': r're:^https?://.*\.jpg' | |
99 | }, | |
db974389 FL |
100 | 'skip': 'The VOD does not exist', |
101 | }, { | |
102 | 'url': 'https://picarto.tv/ArtofZod/videos/772650', | |
103 | 'md5': '00067a0889f1f6869cc512e3e79c521b', | |
104 | 'info_dict': { | |
105 | 'id': '772650', | |
106 | 'ext': 'mp4', | |
107 | 'title': 'Art of Zod - Drawing and Painting', | |
108 | 'thumbnail': r're:^https?://.*\.jpg', | |
109 | 'channel': 'ArtofZod', | |
110 | 'age_limit': 18, | |
111 | } | |
a42839e5 S |
112 | }, { |
113 | 'url': 'https://picarto.tv/videopopout/Plague', | |
114 | 'only_matching': True, | |
115 | }] | |
d6166a76 PG |
116 | |
117 | def _real_extract(self, url): | |
118 | video_id = self._match_id(url) | |
a42839e5 | 119 | |
db974389 FL |
120 | data = self._download_json( |
121 | 'https://ptvintern.picarto.tv/ptvapi', video_id, query={ | |
122 | 'query': f'''{{ | |
123 | video(id: "{video_id}") {{ | |
124 | id | |
125 | title | |
126 | adult | |
127 | file_name | |
128 | video_recording_image_url | |
129 | channel {{ | |
130 | name | |
131 | }} | |
132 | }} | |
133 | }}''' | |
134 | })['data']['video'] | |
135 | ||
136 | file_name = data['file_name'] | |
137 | netloc = urllib.parse.urlparse(data['video_recording_image_url']).netloc | |
a42839e5 S |
138 | |
139 | formats = self._extract_m3u8_formats( | |
db974389 | 140 | f'https://{netloc}/stream/hls/{file_name}/index.m3u8', video_id, 'mp4', m3u8_id='hls') |
d6166a76 PG |
141 | |
142 | return { | |
143 | 'id': video_id, | |
db974389 FL |
144 | **traverse_obj(data, { |
145 | 'id': ('id', {str_or_none}), | |
146 | 'title': ('title', {str}), | |
147 | 'thumbnail': 'video_recording_image_url', | |
148 | 'channel': ('channel', 'name', {str}), | |
149 | 'age_limit': ('adult', {lambda x: 18 if x else 0}), | |
150 | }), | |
a42839e5 | 151 | 'formats': formats, |
d6166a76 | 152 | } |