2 from __future__ import unicode_literals
6 from .common import InfoExtractor
7 from ..compat import compat_str
16 class PicartoIE(InfoExtractor):
17 _VALID_URL = r'https?://(?:www.)?picarto\.tv/(?P<id>[a-zA-Z0-9]+)(?:/(?P<token>[a-zA-Z0-9]+))?'
19 'url': 'https://picarto.tv/Setz',
23 'title': 're:^Setz [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
27 'skip': 'Stream is offline',
31 def suitable(cls, url):
32 return False if PicartoVodIE.suitable(url) else super(PicartoIE, cls).suitable(url)
34 def _real_extract(self, url):
35 channel_id = self._match_id(url)
36 metadata = self._download_json(
37 'https://api.picarto.tv/v1/channel/name/' + channel_id,
40 if metadata.get('online') is False:
41 raise ExtractorError('Stream is offline', expected=True)
43 cdn_data = self._download_json(
44 'https://picarto.tv/process/channel', channel_id,
45 data=urlencode_postdata({'loadbalancinginfo': channel_id}),
46 note='Downloading load balancing info')
48 token = self._VALID_URL_RE.match(url).group('token') or 'public'
50 'con': int(time.time() * 1000),
54 prefered_edge = cdn_data.get('preferedEdge')
57 for edge in cdn_data['edges']:
58 edge_ep = edge.get('ep')
59 if not edge_ep or not isinstance(edge_ep, compat_str):
61 edge_id = edge.get('id')
62 for tech in cdn_data['techs']:
63 tech_label = tech.get('label')
64 tech_type = tech.get('type')
66 if edge_id == prefered_edge:
70 format_id.append(edge_id)
71 if tech_type == 'application/x-mpegurl' or tech_label == 'HLS':
72 format_id.append('hls')
73 formats.extend(self._extract_m3u8_formats(
75 'https://%s/hls/%s/index.m3u8'
76 % (edge_ep, channel_id), params),
77 channel_id, 'mp4', preference=preference,
78 m3u8_id='-'.join(format_id), fatal=False))
80 elif tech_type == 'video/mp4' or tech_label == 'MP4':
81 format_id.append('mp4')
83 'url': update_url_query(
84 'https://%s/mp4/%s.mp4' % (edge_ep, channel_id),
86 'format_id': '-'.join(format_id),
87 'preference': preference,
90 # rtmp format does not seem to work
92 self._sort_formats(formats)
94 mature = metadata.get('adult')
98 age_limit = 18 if mature is True else 0
102 'title': self._live_title(channel_id),
104 'thumbnail': metadata.get('thumbnails', {}).get('web'),
105 'age_limit': age_limit,
110 class PicartoVodIE(InfoExtractor):
111 _VALID_URL = r'https?://(?:www.)?picarto\.tv/videopopout/(?P<id>[^/?#&]+)'
113 'url': 'https://picarto.tv/videopopout/ArtofZod_2017.12.12.00.13.23.flv',
114 'md5': '3ab45ba4352c52ee841a28fb73f2d9ca',
116 'id': 'ArtofZod_2017.12.12.00.13.23.flv',
118 'title': 'ArtofZod_2017.12.12.00.13.23.flv',
119 'thumbnail': r're:^https?://.*\.jpg'
122 'url': 'https://picarto.tv/videopopout/Plague',
123 'only_matching': True,
126 def _real_extract(self, url):
127 video_id = self._match_id(url)
129 webpage = self._download_webpage(url, video_id)
131 vod_info = self._parse_json(
133 r'(?s)#vod-player["\']\s*,\s*(\{.+?\})\s*\)', webpage,
135 video_id, transform_source=js_to_json)
137 formats = self._extract_m3u8_formats(
138 vod_info['vod'], video_id, 'mp4', entry_protocol='m3u8_native',
140 self._sort_formats(formats)
145 'thumbnail': vod_info.get('vodThumb'),