]> jfr.im git - yt-dlp.git/blob - youtube_dlc/extractor/vvvvid.py
Update to ytdl-2021.01.03
[yt-dlp.git] / youtube_dlc / extractor / vvvvid.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5
6 from .common import InfoExtractor
7 from ..utils import (
8 ExtractorError,
9 int_or_none,
10 str_or_none,
11 )
12
13
14 class VVVVIDIE(InfoExtractor):
15 _VALID_URL_BASE = r'https?://(?:www\.)?vvvvid\.it/(?:#!)?(?:show|anime|film|series)/'
16 _VALID_URL = r'%s(?P<show_id>\d+)/[^/]+/(?P<season_id>\d+)/(?P<id>[0-9]+)' % _VALID_URL_BASE
17 _TESTS = [{
18 # video_type == 'video/vvvvid'
19 'url': 'https://www.vvvvid.it/#!show/434/perche-dovrei-guardarlo-di-dario-moccia/437/489048/ping-pong',
20 'md5': 'b8d3cecc2e981adc3835adf07f6df91b',
21 'info_dict': {
22 'id': '489048',
23 'ext': 'mp4',
24 'title': 'Ping Pong',
25 'duration': 239,
26 'series': '"Perché dovrei guardarlo?" di Dario Moccia',
27 'season_id': '437',
28 'episode': 'Ping Pong',
29 'episode_number': 1,
30 'episode_id': '3334',
31 'view_count': int,
32 'like_count': int,
33 'repost_count': int,
34 },
35 'params': {
36 'skip_download': True,
37 },
38 }, {
39 # video_type == 'video/rcs'
40 'url': 'https://www.vvvvid.it/#!show/376/death-note-live-action/377/482493/episodio-01',
41 'md5': '33e0edfba720ad73a8782157fdebc648',
42 'info_dict': {
43 'id': '482493',
44 'ext': 'mp4',
45 'title': 'Episodio 01',
46 },
47 'params': {
48 'skip_download': True,
49 },
50 }, {
51 'url': 'https://www.vvvvid.it/show/434/perche-dovrei-guardarlo-di-dario-moccia/437/489048',
52 'only_matching': True
53 }]
54 _conn_id = None
55
56 def _real_initialize(self):
57 self._conn_id = self._download_json(
58 'https://www.vvvvid.it/user/login',
59 None, headers=self.geo_verification_headers())['data']['conn_id']
60
61 def _download_info(self, show_id, path, video_id, fatal=True):
62 response = self._download_json(
63 'https://www.vvvvid.it/vvvvid/ondemand/%s/%s' % (show_id, path),
64 video_id, headers=self.geo_verification_headers(), query={
65 'conn_id': self._conn_id,
66 }, fatal=fatal)
67 if not (response or fatal):
68 return
69 if response.get('result') == 'error':
70 raise ExtractorError('%s said: %s' % (
71 self.IE_NAME, response['message']), expected=True)
72 return response['data']
73
74 def _extract_common_video_info(self, video_data):
75 return {
76 'thumbnail': video_data.get('thumbnail'),
77 'episode_id': str_or_none(video_data.get('id')),
78 }
79
80 def _real_extract(self, url):
81 show_id, season_id, video_id = re.match(self._VALID_URL, url).groups()
82
83 response = self._download_info(
84 show_id, 'season/%s' % season_id, video_id)
85
86 vid = int(video_id)
87 video_data = list(filter(
88 lambda episode: episode.get('video_id') == vid, response))[0]
89 title = video_data['title']
90 formats = []
91
92 # vvvvid embed_info decryption algorithm is reverse engineered from function $ds(h) at vvvvid.js
93 def ds(h):
94 g = "MNOPIJKL89+/4567UVWXQRSTEFGHABCDcdefYZabstuvopqr0123wxyzklmnghij"
95
96 def f(m):
97 l = []
98 o = 0
99 b = False
100 m_len = len(m)
101 while ((not b) and o < m_len):
102 n = m[o] << 2
103 o += 1
104 k = -1
105 j = -1
106 if o < m_len:
107 n += m[o] >> 4
108 o += 1
109 if o < m_len:
110 k = (m[o - 1] << 4) & 255
111 k += m[o] >> 2
112 o += 1
113 if o < m_len:
114 j = (m[o - 1] << 6) & 255
115 j += m[o]
116 o += 1
117 else:
118 b = True
119 else:
120 b = True
121 else:
122 b = True
123 l.append(n)
124 if k != -1:
125 l.append(k)
126 if j != -1:
127 l.append(j)
128 return l
129
130 c = []
131 for e in h:
132 c.append(g.index(e))
133
134 c_len = len(c)
135 for e in range(c_len * 2 - 1, -1, -1):
136 a = c[e % c_len] ^ c[(e + 1) % c_len]
137 c[e % c_len] = a
138
139 c = f(c)
140 d = ''
141 for e in c:
142 d += chr(e)
143
144 return d
145
146 info = {}
147
148 def metadata_from_url(r_url):
149 if not info and r_url:
150 mobj = re.search(r'_(?:S(\d+))?Ep(\d+)', r_url)
151 if mobj:
152 info['episode_number'] = int(mobj.group(2))
153 season_number = mobj.group(1)
154 if season_number:
155 info['season_number'] = int(season_number)
156
157 for quality in ('_sd', ''):
158 embed_code = video_data.get('embed_info' + quality)
159 if not embed_code:
160 continue
161 embed_code = ds(embed_code)
162 video_type = video_data.get('video_type')
163 if video_type in ('video/rcs', 'video/kenc'):
164 if video_type == 'video/kenc':
165 kenc = self._download_json(
166 'https://www.vvvvid.it/kenc', video_id, query={
167 'action': 'kt',
168 'conn_id': self._conn_id,
169 'url': embed_code,
170 }, fatal=False) or {}
171 kenc_message = kenc.get('message')
172 if kenc_message:
173 embed_code += '?' + ds(kenc_message)
174 formats.extend(self._extract_akamai_formats(embed_code, video_id))
175 else:
176 formats.extend(self._extract_wowza_formats(
177 'http://sb.top-ix.org/videomg/_definst_/mp4:%s/playlist.m3u8' % embed_code, video_id))
178 metadata_from_url(embed_code)
179
180 self._sort_formats(formats)
181
182 metadata_from_url(video_data.get('thumbnail'))
183 info.update(self._extract_common_video_info(video_data))
184 info.update({
185 'id': video_id,
186 'title': title,
187 'formats': formats,
188 'duration': int_or_none(video_data.get('length')),
189 'series': video_data.get('show_title'),
190 'season_id': season_id,
191 'episode': title,
192 'view_count': int_or_none(video_data.get('views')),
193 'like_count': int_or_none(video_data.get('video_likes')),
194 'repost_count': int_or_none(video_data.get('video_shares')),
195 })
196 return info
197
198
199 class VVVVIDShowIE(VVVVIDIE):
200 _VALID_URL = r'(?P<base_url>%s(?P<id>\d+)(?:/(?P<show_title>[^/?&#]+))?)/?(?:[?#&]|$)' % VVVVIDIE._VALID_URL_BASE
201 _TESTS = [{
202 'url': 'https://www.vvvvid.it/show/156/psyco-pass',
203 'info_dict': {
204 'id': '156',
205 'title': 'Psycho-Pass',
206 'description': 'md5:94d572c0bd85894b193b8aebc9a3a806',
207 },
208 'playlist_count': 46,
209 }, {
210 'url': 'https://www.vvvvid.it/show/156',
211 'only_matching': True,
212 }]
213
214 def _real_extract(self, url):
215 base_url, show_id, show_title = re.match(self._VALID_URL, url).groups()
216
217 seasons = self._download_info(
218 show_id, 'seasons/', show_title)
219
220 show_info = self._download_info(
221 show_id, 'info/', show_title, fatal=False)
222
223 entries = []
224 for season in (seasons or []):
225 episodes = season.get('episodes') or []
226 for episode in episodes:
227 if episode.get('playable') is False:
228 continue
229 season_id = str_or_none(episode.get('season_id'))
230 video_id = str_or_none(episode.get('video_id'))
231 if not (season_id and video_id):
232 continue
233 info = self._extract_common_video_info(episode)
234 info.update({
235 '_type': 'url',
236 'ie_key': VVVVIDIE.ie_key(),
237 'url': '/'.join([base_url, season_id, video_id]),
238 'title': episode.get('title'),
239 'description': episode.get('description'),
240 'season_id': season_id,
241 })
242 entries.append(info)
243
244 return self.playlist_result(
245 entries, show_id, show_info.get('title'), show_info.get('description'))