]>
Commit | Line | Data |
---|---|---|
5cc09c00 M |
1 | import base64 |
2 | ||
345b4c0a | 3 | from .common import InfoExtractor |
4 | from ..utils import ( | |
5 | ExtractorError, | |
6 | extract_attributes, | |
7 | int_or_none, | |
8 | str_or_none, | |
9 | traverse_obj, | |
5cc09c00 | 10 | try_call, |
345b4c0a | 11 | unescapeHTML, |
12 | url_or_none, | |
13 | ) | |
14 | ||
15 | ||
5cc09c00 M |
16 | class ZaikoBaseIE(InfoExtractor): |
17 | def _download_real_webpage(self, url, video_id): | |
18 | webpage, urlh = self._download_webpage_handle(url, video_id) | |
19 | final_url = urlh.geturl() | |
20 | if 'zaiko.io/login' in final_url: | |
21 | self.raise_login_required() | |
22 | elif '/_buy/' in final_url: | |
23 | raise ExtractorError('Your account does not have tickets to this event', expected=True) | |
24 | return webpage | |
25 | ||
26 | def _parse_vue_element_attr(self, name, string, video_id): | |
27 | page_elem = self._search_regex(rf'(<{name}[^>]+>)', string, name) | |
28 | attrs = {} | |
29 | for key, value in extract_attributes(page_elem).items(): | |
30 | if key.startswith(':'): | |
31 | attrs[key[1:]] = self._parse_json( | |
32 | value, video_id, transform_source=unescapeHTML, fatal=False) | |
33 | return attrs | |
34 | ||
35 | ||
36 | class ZaikoIE(ZaikoBaseIE): | |
345b4c0a | 37 | _VALID_URL = r'https?://(?:[\w-]+\.)?zaiko\.io/event/(?P<id>\d+)/stream(?:/\d+)+' |
38 | _TESTS = [{ | |
39 | 'url': 'https://zaiko.io/event/324868/stream/20571/20571', | |
40 | 'info_dict': { | |
41 | 'id': '324868', | |
42 | 'ext': 'mp4', | |
43 | 'title': 'ZAIKO STREAMING TEST', | |
44 | 'alt_title': '[VOD] ZAIKO STREAMING TEST_20210603(Do Not Delete)', | |
45 | 'uploader_id': '454', | |
46 | 'uploader': 'ZAIKO ZERO', | |
47 | 'release_timestamp': 1583809200, | |
48 | 'thumbnail': r're:https://[a-z0-9]+.cloudfront.net/[a-z0-9_]+/[a-z0-9_]+', | |
49 | 'release_date': '20200310', | |
50 | 'categories': ['Tech House'], | |
51 | 'live_status': 'was_live', | |
52 | }, | |
53 | 'params': {'skip_download': 'm3u8'}, | |
54 | }] | |
55 | ||
345b4c0a | 56 | def _real_extract(self, url): |
57 | video_id = self._match_id(url) | |
58 | ||
5cc09c00 | 59 | webpage = self._download_real_webpage(url, video_id) |
345b4c0a | 60 | stream_meta = self._parse_vue_element_attr('stream-page', webpage, video_id) |
61 | ||
62 | player_page = self._download_webpage( | |
63 | stream_meta['stream-access']['video_source'], video_id, | |
64 | 'Downloading player page', headers={'referer': 'https://zaiko.io/'}) | |
65 | player_meta = self._parse_vue_element_attr('player', player_page, video_id) | |
66 | status = traverse_obj(player_meta, ('initial_event_info', 'status', {str})) | |
67 | live_status, msg, expected = { | |
68 | 'vod': ('was_live', 'No VOD stream URL was found', False), | |
69 | 'archiving': ('post_live', 'Event VOD is still being processed', True), | |
70 | 'deleting': ('post_live', 'This event has ended', True), | |
71 | 'deleted': ('post_live', 'This event has ended', True), | |
72 | 'error': ('post_live', 'This event has ended', True), | |
73 | 'disconnected': ('post_live', 'Stream has been disconnected', True), | |
74 | 'live_to_disconnected': ('post_live', 'Stream has been disconnected', True), | |
75 | 'live': ('is_live', 'No livestream URL found was found', False), | |
76 | 'waiting': ('is_upcoming', 'Live event has not yet started', True), | |
77 | 'cancelled': ('not_live', 'Event has been cancelled', True), | |
78 | }.get(status) or ('not_live', f'Unknown event status "{status}"', False) | |
79 | ||
80 | stream_url = traverse_obj(player_meta, ('initial_event_info', 'endpoint', {url_or_none})) | |
81 | formats = self._extract_m3u8_formats( | |
82 | stream_url, video_id, live=True, fatal=False) if stream_url else [] | |
83 | if not formats: | |
84 | self.raise_no_formats(msg, expected=expected) | |
85 | ||
86 | return { | |
87 | 'id': video_id, | |
88 | 'formats': formats, | |
89 | 'live_status': live_status, | |
90 | **traverse_obj(stream_meta, { | |
91 | 'title': ('event', 'name', {str}), | |
92 | 'uploader': ('profile', 'name', {str}), | |
93 | 'uploader_id': ('profile', 'id', {str_or_none}), | |
94 | 'release_timestamp': ('stream', 'start', 'timestamp', {int_or_none}), | |
95 | 'categories': ('event', 'genres', ..., {lambda x: x or None}), | |
96 | }), | |
97 | **traverse_obj(player_meta, ('initial_event_info', { | |
98 | 'alt_title': ('title', {str}), | |
99 | 'thumbnail': ('poster_url', {url_or_none}), | |
100 | })), | |
101 | } | |
5cc09c00 M |
102 | |
103 | ||
104 | class ZaikoETicketIE(ZaikoBaseIE): | |
105 | _VALID_URL = r'https?://(?:www.)?zaiko\.io/account/eticket/(?P<id>[\w=-]{49})' | |
106 | _TESTS = [{ | |
107 | 'url': 'https://zaiko.io/account/eticket/TZjMwMzQ2Y2EzMXwyMDIzMDYwNzEyMTMyNXw1MDViOWU2Mw==', | |
108 | 'playlist_count': 1, | |
109 | 'info_dict': { | |
110 | 'id': 'f30346ca31-20230607121325-505b9e63', | |
111 | 'title': 'ZAIKO STREAMING TEST', | |
112 | 'thumbnail': 'https://media.zkocdn.net/pf_1/1_3wdyjcjyupseatkwid34u', | |
113 | }, | |
114 | 'skip': 'Only available with the ticketholding account', | |
115 | }] | |
116 | ||
117 | def _real_extract(self, url): | |
118 | ticket_id = self._match_id(url) | |
119 | ticket_id = try_call( | |
120 | lambda: base64.urlsafe_b64decode(ticket_id[1:]).decode().replace('|', '-')) or ticket_id | |
121 | ||
122 | webpage = self._download_real_webpage(url, ticket_id) | |
123 | eticket = self._parse_vue_element_attr('eticket', webpage, ticket_id) | |
124 | ||
125 | return self.playlist_result( | |
126 | [self.url_result(stream, ZaikoIE) for stream in traverse_obj(eticket, ('streams', ..., 'url'))], | |
127 | ticket_id, **traverse_obj(eticket, ('ticket-details', { | |
128 | 'title': 'event_name', | |
129 | 'thumbnail': 'event_img_url', | |
130 | }))) |