]>
Commit | Line | Data |
---|---|---|
9bd05b5a YCH |
1 | from __future__ import unicode_literals |
2 | ||
3 | import base64 | |
4 | import json | |
5 | import random | |
6 | import re | |
7 | ||
8 | from .common import InfoExtractor | |
9 | from ..aes import ( | |
10 | aes_cbc_decrypt, | |
11 | aes_cbc_encrypt, | |
12 | ) | |
cf282071 | 13 | from ..compat import compat_b64decode |
9bd05b5a YCH |
14 | from ..utils import ( |
15 | bytes_to_intlist, | |
16 | bytes_to_long, | |
5f699251 | 17 | extract_attributes, |
9bd05b5a YCH |
18 | ExtractorError, |
19 | intlist_to_bytes, | |
9bd05b5a YCH |
20 | js_to_json, |
21 | int_or_none, | |
22 | long_to_bytes, | |
23 | pkcs1pad, | |
9bd05b5a YCH |
24 | ) |
25 | ||
26 | ||
5f699251 RA |
27 | class DaisukiMottoIE(InfoExtractor): |
28 | _VALID_URL = r'https?://motto\.daisuki\.net/framewatch/embed/[^/]+/(?P<id>[0-9a-zA-Z]{3})' | |
9bd05b5a YCH |
29 | |
30 | _TEST = { | |
5f699251 | 31 | 'url': 'http://motto.daisuki.net/framewatch/embed/embedDRAGONBALLSUPERUniverseSurvivalsaga/V2e/760/428', |
9bd05b5a | 32 | 'info_dict': { |
5f699251 | 33 | 'id': 'V2e', |
9bd05b5a | 34 | 'ext': 'mp4', |
5f699251 | 35 | 'title': '#117 SHOWDOWN OF LOVE! ANDROIDS VS UNIVERSE 2!!', |
7c4aa6fd YCH |
36 | 'subtitles': { |
37 | 'mul': [{ | |
38 | 'ext': 'ttml', | |
39 | }], | |
40 | }, | |
9bd05b5a YCH |
41 | }, |
42 | 'params': { | |
43 | 'skip_download': True, # AES-encrypted HLS stream | |
44 | }, | |
45 | } | |
46 | ||
47 | # The public key in PEM format can be found in clientlibs_anime_watch.min.js | |
48 | _RSA_KEY = (0xc5524c25e8e14b366b3754940beeb6f96cb7e2feef0b932c7659a0c5c3bf173d602464c2df73d693b513ae06ff1be8f367529ab30bf969c5640522181f2a0c51ea546ae120d3d8d908595e4eff765b389cde080a1ef7f1bbfb07411cc568db73b7f521cedf270cbfbe0ddbc29b1ac9d0f2d8f4359098caffee6d07915020077d, 65537) | |
49 | ||
50 | def _real_extract(self, url): | |
51 | video_id = self._match_id(url) | |
52 | ||
53 | webpage = self._download_webpage(url, video_id) | |
54 | ||
55 | flashvars = self._parse_json(self._search_regex( | |
56 | r'(?s)var\s+flashvars\s*=\s*({.+?});', webpage, 'flashvars'), | |
57 | video_id, transform_source=js_to_json) | |
58 | ||
59 | iv = [0] * 16 | |
60 | ||
61 | data = {} | |
62 | for key in ('device_cd', 'mv_id', 'ss1_prm', 'ss2_prm', 'ss3_prm', 'ss_id'): | |
63 | data[key] = flashvars.get(key, '') | |
64 | ||
65 | encrypted_rtn = None | |
66 | ||
67 | # Some AES keys are rejected. Try it with different AES keys | |
68 | for idx in range(5): | |
69 | aes_key = [random.randint(0, 254) for _ in range(32)] | |
70 | padded_aeskey = intlist_to_bytes(pkcs1pad(aes_key, 128)) | |
71 | ||
72 | n, e = self._RSA_KEY | |
73 | encrypted_aeskey = long_to_bytes(pow(bytes_to_long(padded_aeskey), e, n)) | |
5f699251 RA |
74 | init_data = self._download_json( |
75 | 'http://motto.daisuki.net/fastAPI/bgn/init/', | |
76 | video_id, query={ | |
77 | 's': flashvars.get('s', ''), | |
78 | 'c': flashvars.get('ss3_prm', ''), | |
79 | 'e': url, | |
80 | 'd': base64.b64encode(intlist_to_bytes(aes_cbc_encrypt( | |
81 | bytes_to_intlist(json.dumps(data)), | |
82 | aes_key, iv))).decode('ascii'), | |
83 | 'a': base64.b64encode(encrypted_aeskey).decode('ascii'), | |
84 | }, note='Downloading JSON metadata' + (' (try #%d)' % (idx + 1) if idx > 0 else '')) | |
9bd05b5a YCH |
85 | |
86 | if 'rtn' in init_data: | |
87 | encrypted_rtn = init_data['rtn'] | |
88 | break | |
89 | ||
90 | self._sleep(5, video_id) | |
91 | ||
92 | if encrypted_rtn is None: | |
93 | raise ExtractorError('Failed to fetch init data') | |
94 | ||
95 | rtn = self._parse_json( | |
96 | intlist_to_bytes(aes_cbc_decrypt(bytes_to_intlist( | |
cf282071 | 97 | compat_b64decode(encrypted_rtn)), |
9bd05b5a YCH |
98 | aes_key, iv)).decode('utf-8').rstrip('\0'), |
99 | video_id) | |
100 | ||
5f699251 RA |
101 | title = rtn['title_str'] |
102 | ||
9bd05b5a YCH |
103 | formats = self._extract_m3u8_formats( |
104 | rtn['play_url'], video_id, ext='mp4', entry_protocol='m3u8_native') | |
105 | ||
7c4aa6fd YCH |
106 | subtitles = {} |
107 | caption_url = rtn.get('caption_url') | |
108 | if caption_url: | |
109 | # mul: multiple languages | |
110 | subtitles['mul'] = [{ | |
111 | 'url': caption_url, | |
112 | 'ext': 'ttml', | |
113 | }] | |
114 | ||
9bd05b5a YCH |
115 | return { |
116 | 'id': video_id, | |
117 | 'title': title, | |
118 | 'formats': formats, | |
7c4aa6fd | 119 | 'subtitles': subtitles, |
9bd05b5a YCH |
120 | } |
121 | ||
122 | ||
5f699251 RA |
123 | class DaisukiMottoPlaylistIE(InfoExtractor): |
124 | _VALID_URL = r'https?://motto\.daisuki\.net/(?P<id>information)/' | |
9bd05b5a YCH |
125 | |
126 | _TEST = { | |
5f699251 | 127 | 'url': 'http://motto.daisuki.net/information/', |
9bd05b5a | 128 | 'info_dict': { |
5f699251 | 129 | 'title': 'DRAGON BALL SUPER', |
9bd05b5a | 130 | }, |
5f699251 | 131 | 'playlist_mincount': 117, |
9bd05b5a YCH |
132 | } |
133 | ||
134 | def _real_extract(self, url): | |
135 | playlist_id = self._match_id(url) | |
136 | ||
137 | webpage = self._download_webpage(url, playlist_id) | |
138 | ||
5f699251 RA |
139 | entries = [] |
140 | for li in re.findall(r'(<li[^>]+?data-product_id="[a-zA-Z0-9]{3}"[^>]+>)', webpage): | |
141 | attr = extract_attributes(li) | |
142 | ad_id = attr.get('data-ad_id') | |
143 | product_id = attr.get('data-product_id') | |
144 | if ad_id and product_id: | |
145 | episode_id = attr.get('data-chapter') | |
146 | entries.append({ | |
147 | '_type': 'url_transparent', | |
148 | 'url': 'http://motto.daisuki.net/framewatch/embed/%s/%s/760/428' % (ad_id, product_id), | |
149 | 'episode_id': episode_id, | |
150 | 'episode_number': int_or_none(episode_id), | |
151 | 'ie_key': 'DaisukiMotto', | |
152 | }) | |
153 | ||
154 | return self.playlist_result(entries, playlist_title='DRAGON BALL SUPER') |