]>
Commit | Line | Data |
---|---|---|
d6166a76 PG |
1 | # coding: utf-8 |
2 | from __future__ import unicode_literals | |
3 | ||
4 | from .common import InfoExtractor | |
a42839e5 S |
5 | from ..utils import ( |
6 | ExtractorError, | |
7 | js_to_json, | |
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 PG |
70 | self._sort_formats(formats) |
71 | ||
f17a24a6 | 72 | mature = metadata.get('adult') |
a42839e5 S |
73 | if mature is None: |
74 | age_limit = None | |
75 | else: | |
76 | age_limit = 18 if mature is True else 0 | |
77 | ||
d6166a76 PG |
78 | return { |
79 | 'id': channel_id, | |
cce889b9 | 80 | 'title': self._live_title(title.strip()), |
d6166a76 | 81 | 'is_live': True, |
730c0d12 | 82 | 'channel': channel_id, |
cce889b9 | 83 | 'channel_id': metadata.get('id'), |
730c0d12 | 84 | 'channel_url': 'https://picarto.tv/%s' % channel_id, |
a42839e5 S |
85 | 'age_limit': age_limit, |
86 | 'formats': formats, | |
d6166a76 PG |
87 | } |
88 | ||
89 | ||
90 | class PicartoVodIE(InfoExtractor): | |
a42839e5 S |
91 | _VALID_URL = r'https?://(?:www.)?picarto\.tv/videopopout/(?P<id>[^/?#&]+)' |
92 | _TESTS = [{ | |
93 | 'url': 'https://picarto.tv/videopopout/ArtofZod_2017.12.12.00.13.23.flv', | |
94 | 'md5': '3ab45ba4352c52ee841a28fb73f2d9ca', | |
d6166a76 | 95 | 'info_dict': { |
a42839e5 | 96 | 'id': 'ArtofZod_2017.12.12.00.13.23.flv', |
d6166a76 | 97 | 'ext': 'mp4', |
a42839e5 S |
98 | 'title': 'ArtofZod_2017.12.12.00.13.23.flv', |
99 | 'thumbnail': r're:^https?://.*\.jpg' | |
100 | }, | |
101 | }, { | |
102 | 'url': 'https://picarto.tv/videopopout/Plague', | |
103 | 'only_matching': True, | |
104 | }] | |
d6166a76 PG |
105 | |
106 | def _real_extract(self, url): | |
107 | video_id = self._match_id(url) | |
a42839e5 | 108 | |
d6166a76 PG |
109 | webpage = self._download_webpage(url, video_id) |
110 | ||
a42839e5 S |
111 | vod_info = self._parse_json( |
112 | self._search_regex( | |
113 | r'(?s)#vod-player["\']\s*,\s*(\{.+?\})\s*\)', webpage, | |
114 | video_id), | |
115 | video_id, transform_source=js_to_json) | |
116 | ||
117 | formats = self._extract_m3u8_formats( | |
118 | vod_info['vod'], video_id, 'mp4', entry_protocol='m3u8_native', | |
119 | m3u8_id='hls') | |
120 | self._sort_formats(formats) | |
d6166a76 PG |
121 | |
122 | return { | |
123 | 'id': video_id, | |
124 | 'title': video_id, | |
d6166a76 | 125 | 'thumbnail': vod_info.get('vodThumb'), |
a42839e5 | 126 | 'formats': formats, |
d6166a76 | 127 | } |