]>
Commit | Line | Data |
---|---|---|
82be732b RA |
1 | # coding: utf-8 |
2 | from __future__ import unicode_literals | |
3 | ||
4 | import base64 | |
5 | import json | |
6 | import os | |
7 | ||
8 | from .common import InfoExtractor | |
9 | from ..aes import aes_cbc_decrypt | |
10 | from ..compat import compat_ord | |
11 | from ..utils import ( | |
12 | bytes_to_intlist, | |
13 | ExtractorError, | |
14 | float_or_none, | |
15 | intlist_to_bytes, | |
16 | srt_subtitles_timecode, | |
17 | strip_or_none, | |
20e2c9de | 18 | urljoin, |
82be732b RA |
19 | ) |
20 | ||
21 | ||
22 | class ADNIE(InfoExtractor): | |
23 | IE_DESC = 'Anime Digital Network' | |
24 | _VALID_URL = r'https?://(?:www\.)?animedigitalnetwork\.fr/video/[^/]+/(?P<id>\d+)' | |
25 | _TEST = { | |
26 | 'url': 'http://animedigitalnetwork.fr/video/blue-exorcist-kyoto-saga/7778-episode-1-debut-des-hostilites', | |
27 | 'md5': 'e497370d847fd79d9d4c74be55575c7a', | |
28 | 'info_dict': { | |
29 | 'id': '7778', | |
30 | 'ext': 'mp4', | |
31 | 'title': 'Blue Exorcist - Kyôto Saga - Épisode 1', | |
32 | 'description': 'md5:2f7b5aa76edbc1a7a92cedcda8a528d5', | |
33 | } | |
34 | } | |
20e2c9de | 35 | _BASE_URL = 'http://animedigitalnetwork.fr' |
82be732b RA |
36 | |
37 | def _get_subtitles(self, sub_path, video_id): | |
38 | if not sub_path: | |
39 | return None | |
40 | ||
41 | enc_subtitles = self._download_webpage( | |
20e2c9de RA |
42 | urljoin(self._BASE_URL, sub_path), |
43 | video_id, fatal=False, headers={ | |
44 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0', | |
45 | }) | |
82be732b RA |
46 | if not enc_subtitles: |
47 | return None | |
48 | ||
49 | # http://animedigitalnetwork.fr/components/com_vodvideo/videojs/adn-vjs.min.js | |
50 | dec_subtitles = intlist_to_bytes(aes_cbc_decrypt( | |
51 | bytes_to_intlist(base64.b64decode(enc_subtitles[24:])), | |
20e2c9de | 52 | bytes_to_intlist(b'\x1b\xe0\x29\x61\x38\x94\x24\x00\x12\xbd\xc5\x80\xac\xce\xbe\xb0'), |
82be732b RA |
53 | bytes_to_intlist(base64.b64decode(enc_subtitles[:24])) |
54 | )) | |
55 | subtitles_json = self._parse_json( | |
20e2c9de | 56 | dec_subtitles[:-compat_ord(dec_subtitles[-1])].decode(), |
82be732b RA |
57 | None, fatal=False) |
58 | if not subtitles_json: | |
59 | return None | |
60 | ||
61 | subtitles = {} | |
62 | for sub_lang, sub in subtitles_json.items(): | |
63 | srt = '' | |
64 | for num, current in enumerate(sub): | |
65 | start, end, text = ( | |
66 | float_or_none(current.get('startTime')), | |
67 | float_or_none(current.get('endTime')), | |
68 | current.get('text')) | |
69 | if start is None or end is None or text is None: | |
70 | continue | |
71 | srt += os.linesep.join( | |
72 | ( | |
73 | '%d' % num, | |
74 | '%s --> %s' % ( | |
75 | srt_subtitles_timecode(start), | |
76 | srt_subtitles_timecode(end)), | |
77 | text, | |
78 | os.linesep, | |
79 | )) | |
80 | ||
81 | if sub_lang == 'vostf': | |
82 | sub_lang = 'fr' | |
83 | subtitles.setdefault(sub_lang, []).extend([{ | |
84 | 'ext': 'json', | |
85 | 'data': json.dumps(sub), | |
86 | }, { | |
87 | 'ext': 'srt', | |
88 | 'data': srt, | |
89 | }]) | |
90 | return subtitles | |
91 | ||
92 | def _real_extract(self, url): | |
93 | video_id = self._match_id(url) | |
94 | webpage = self._download_webpage(url, video_id) | |
95 | player_config = self._parse_json(self._search_regex( | |
96 | r'playerConfig\s*=\s*({.+});', webpage, 'player config'), video_id) | |
97 | ||
98 | video_info = {} | |
99 | video_info_str = self._search_regex( | |
100 | r'videoInfo\s*=\s*({.+});', webpage, | |
101 | 'video info', fatal=False) | |
102 | if video_info_str: | |
103 | video_info = self._parse_json( | |
104 | video_info_str, video_id, fatal=False) or {} | |
105 | ||
106 | options = player_config.get('options') or {} | |
107 | metas = options.get('metas') or {} | |
108 | title = metas.get('title') or video_info['title'] | |
109 | links = player_config.get('links') or {} | |
20e2c9de RA |
110 | if not links: |
111 | links_url = player_config['linksurl'] | |
112 | links_data = self._download_json(urljoin( | |
113 | self._BASE_URL, links_url), video_id) | |
114 | links = links_data.get('links') or {} | |
82be732b RA |
115 | |
116 | formats = [] | |
117 | for format_id, qualities in links.items(): | |
20e2c9de RA |
118 | if not isinstance(qualities, dict): |
119 | continue | |
82be732b RA |
120 | for load_balancer_url in qualities.values(): |
121 | load_balancer_data = self._download_json( | |
122 | load_balancer_url, video_id, fatal=False) or {} | |
123 | m3u8_url = load_balancer_data.get('location') | |
124 | if not m3u8_url: | |
125 | continue | |
126 | m3u8_formats = self._extract_m3u8_formats( | |
127 | m3u8_url, video_id, 'mp4', 'm3u8_native', | |
128 | m3u8_id=format_id, fatal=False) | |
129 | if format_id == 'vf': | |
130 | for f in m3u8_formats: | |
131 | f['language'] = 'fr' | |
132 | formats.extend(m3u8_formats) | |
133 | error = options.get('error') | |
134 | if not formats and error: | |
135 | raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True) | |
136 | self._sort_formats(formats) | |
137 | ||
138 | return { | |
139 | 'id': video_id, | |
140 | 'title': title, | |
141 | 'description': strip_or_none(metas.get('summary') or video_info.get('resume')), | |
142 | 'thumbnail': video_info.get('image'), | |
143 | 'formats': formats, | |
144 | 'subtitles': self.extract_subtitles(player_config.get('subtitles'), video_id), | |
145 | 'episode': metas.get('subtitle') or video_info.get('videoTitle'), | |
146 | 'series': video_info.get('playlistTitle'), | |
147 | } |