]>
Commit | Line | Data |
---|---|---|
1bf32108 S |
1 | # encoding: utf-8 |
2 | from __future__ import unicode_literals | |
3 | ||
4 | import re | |
75229763 S |
5 | import time |
6 | import hashlib | |
1bf32108 S |
7 | |
8 | from .common import InfoExtractor | |
1cc79574 PH |
9 | from ..compat import ( |
10 | compat_str, | |
2469a6ae | 11 | compat_urlparse, |
1cc79574 PH |
12 | ) |
13 | from ..utils import ( | |
c63b3090 | 14 | clean_html, |
1cc79574 | 15 | ExtractorError, |
815ac029 S |
16 | int_or_none, |
17 | float_or_none, | |
18 | parse_iso8601, | |
5c2266df | 19 | sanitized_Request, |
6e6bc8da | 20 | urlencode_postdata, |
1bf32108 S |
21 | ) |
22 | ||
23 | ||
24 | class NocoIE(InfoExtractor): | |
5886b38d | 25 | _VALID_URL = r'https?://(?:(?:www\.)?noco\.tv/emission/|player\.noco\.tv/\?idvideo=)(?P<id>\d+)' |
c63b3090 | 26 | _LOGIN_URL = 'http://noco.tv/do.php' |
75229763 | 27 | _API_URL_TEMPLATE = 'https://api.noco.tv/1.1/%s?ts=%s&tk=%s' |
cb6444e1 | 28 | _SUB_LANG_TEMPLATE = '&sub_lang=%s' |
c63b3090 | 29 | _NETRC_MACHINE = 'noco' |
1bf32108 | 30 | |
ff9d68e7 AD |
31 | _TESTS = [ |
32 | { | |
33 | 'url': 'http://noco.tv/emission/11538/nolife/ami-ami-idol-hello-france/', | |
34 | 'md5': '0a993f0058ddbcd902630b2047ef710e', | |
35 | 'info_dict': { | |
36 | 'id': '11538', | |
37 | 'ext': 'mp4', | |
38 | 'title': 'Ami Ami Idol - Hello! France', | |
39 | 'description': 'md5:4eaab46ab68fa4197a317a88a53d3b86', | |
40 | 'upload_date': '20140412', | |
41 | 'uploader': 'Nolife', | |
42 | 'uploader_id': 'NOL', | |
43 | 'duration': 2851.2, | |
44 | }, | |
45 | 'skip': 'Requires noco account', | |
d7e7dedb | 46 | }, |
ff9d68e7 AD |
47 | { |
48 | 'url': 'http://noco.tv/emission/12610/lbl42/the-guild/s01e01-wake-up-call', | |
49 | 'md5': 'c190f1f48e313c55838f1f412225934d', | |
50 | 'info_dict': { | |
51 | 'id': '12610', | |
52 | 'ext': 'mp4', | |
53 | 'title': 'The Guild #1 - Wake-Up Call', | |
01e4b1ee | 54 | 'timestamp': 1403863200, |
ff9d68e7 AD |
55 | 'upload_date': '20140627', |
56 | 'uploader': 'LBL42', | |
57 | 'uploader_id': 'LBL', | |
58 | 'duration': 233.023, | |
59 | }, | |
60 | 'skip': 'Requires noco account', | |
61 | } | |
62 | ] | |
1bf32108 | 63 | |
c63b3090 S |
64 | def _real_initialize(self): |
65 | self._login() | |
66 | ||
67 | def _login(self): | |
68 | (username, password) = self._get_login_info() | |
69 | if username is None: | |
70 | return | |
71 | ||
72 | login_form = { | |
73 | 'a': 'login', | |
74 | 'cookie': '1', | |
75 | 'username': username, | |
76 | 'password': password, | |
77 | } | |
6e6bc8da | 78 | request = sanitized_Request(self._LOGIN_URL, urlencode_postdata(login_form)) |
c63b3090 S |
79 | request.add_header('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8') |
80 | ||
81 | login = self._download_json(request, None, 'Logging in as %s' % username) | |
82 | ||
83 | if 'erreur' in login: | |
75229763 S |
84 | raise ExtractorError('Unable to login: %s' % clean_html(login['erreur']), expected=True) |
85 | ||
2469a6ae S |
86 | @staticmethod |
87 | def _ts(): | |
88 | return int(time.time() * 1000) | |
89 | ||
cb6444e1 | 90 | def _call_api(self, path, video_id, note, sub_lang=None): |
2469a6ae | 91 | ts = compat_str(self._ts() + self._ts_offset) |
f7d159cf | 92 | tk = hashlib.md5((hashlib.md5(ts.encode('ascii')).hexdigest() + '#8S?uCraTedap6a').encode('ascii')).hexdigest() |
75229763 | 93 | url = self._API_URL_TEMPLATE % (path, ts, tk) |
cb6444e1 S |
94 | if sub_lang: |
95 | url += self._SUB_LANG_TEMPLATE % sub_lang | |
75229763 | 96 | |
2469a6ae S |
97 | request = sanitized_Request(url) |
98 | request.add_header('Referer', self._referer) | |
99 | ||
100 | resp = self._download_json(request, video_id, note) | |
75229763 S |
101 | |
102 | if isinstance(resp, dict) and resp.get('error'): | |
103 | self._raise_error(resp['error'], resp['description']) | |
104 | ||
105 | return resp | |
106 | ||
107 | def _raise_error(self, error, description): | |
108 | raise ExtractorError( | |
109 | '%s returned error: %s - %s' % (self.IE_NAME, error, description), | |
110 | expected=True) | |
c63b3090 | 111 | |
1bf32108 | 112 | def _real_extract(self, url): |
7824e1f6 | 113 | video_id = self._match_id(url) |
1bf32108 | 114 | |
2469a6ae S |
115 | # Timestamp adjustment offset between server time and local time |
116 | # must be calculated in order to use timestamps closest to server's | |
117 | # in all API requests (see https://github.com/rg3/youtube-dl/issues/7864) | |
118 | webpage = self._download_webpage(url, video_id) | |
119 | ||
120 | player_url = self._search_regex( | |
121 | r'(["\'])(?P<player>https?://noco\.tv/(?:[^/]+/)+NocoPlayer.+?\.swf.*?)\1', | |
122 | webpage, 'noco player', group='player', | |
123 | default='http://noco.tv/cdata/js/player/NocoPlayer-v1.2.40.swf') | |
124 | ||
125 | qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(player_url).query) | |
126 | ts = int_or_none(qs.get('ts', [None])[0]) | |
127 | self._ts_offset = ts - self._ts() if ts else 0 | |
128 | self._referer = player_url | |
129 | ||
75229763 S |
130 | medias = self._call_api( |
131 | 'shows/%s/medias' % video_id, | |
132 | video_id, 'Downloading video JSON') | |
133 | ||
7212560f AD |
134 | show = self._call_api( |
135 | 'shows/by_id/%s' % video_id, | |
136 | video_id, 'Downloading show JSON')[0] | |
137 | ||
6568382d S |
138 | options = self._call_api( |
139 | 'users/init', video_id, | |
140 | 'Downloading user options JSON')['options'] | |
141 | audio_lang_pref = options.get('audio_language') or options.get('language', 'fr') | |
142 | ||
143 | if audio_lang_pref == 'original': | |
144 | audio_lang_pref = show['original_lang'] | |
7212560f | 145 | if len(medias) == 1: |
6568382d S |
146 | audio_lang_pref = list(medias.keys())[0] |
147 | elif audio_lang_pref not in medias: | |
148 | audio_lang_pref = 'fr' | |
7212560f | 149 | |
75229763 S |
150 | qualities = self._call_api( |
151 | 'qualities', | |
152 | video_id, 'Downloading qualities JSON') | |
1bf32108 S |
153 | |
154 | formats = [] | |
155 | ||
6568382d S |
156 | for audio_lang, audio_lang_dict in medias.items(): |
157 | preference = 1 if audio_lang == audio_lang_pref else 0 | |
158 | for sub_lang, lang_dict in audio_lang_dict['video_list'].items(): | |
159 | for format_id, fmt in lang_dict['quality_list'].items(): | |
160 | format_id_extended = 'audio-%s_sub-%s_%s' % (audio_lang, sub_lang, format_id) | |
161 | ||
162 | video = self._call_api( | |
163 | 'shows/%s/video/%s/%s' % (video_id, format_id.lower(), audio_lang), | |
164 | video_id, 'Downloading %s video JSON' % format_id_extended, | |
165 | sub_lang if sub_lang != 'none' else None) | |
166 | ||
167 | file_url = video['file'] | |
168 | if not file_url: | |
169 | continue | |
170 | ||
171 | if file_url in ['forbidden', 'not found']: | |
172 | popmessage = video['popmessage'] | |
173 | self._raise_error(popmessage['title'], popmessage['message']) | |
174 | ||
175 | formats.append({ | |
176 | 'url': file_url, | |
177 | 'format_id': format_id_extended, | |
815ac029 S |
178 | 'width': int_or_none(fmt.get('res_width')), |
179 | 'height': int_or_none(fmt.get('res_lines')), | |
9dc1d94a S |
180 | 'abr': int_or_none(fmt.get('audiobitrate'), 1000), |
181 | 'vbr': int_or_none(fmt.get('videobitrate'), 1000), | |
815ac029 S |
182 | 'filesize': int_or_none(fmt.get('filesize')), |
183 | 'format_note': qualities[format_id].get('quality_name'), | |
184 | 'quality': qualities[format_id].get('priority'), | |
6568382d S |
185 | 'preference': preference, |
186 | }) | |
1bf32108 S |
187 | |
188 | self._sort_formats(formats) | |
189 | ||
815ac029 | 190 | timestamp = parse_iso8601(show.get('online_date_start_utc'), ' ') |
01e21b89 YCH |
191 | |
192 | if timestamp is not None and timestamp < 0: | |
193 | timestamp = None | |
194 | ||
815ac029 S |
195 | uploader = show.get('partner_name') |
196 | uploader_id = show.get('partner_key') | |
197 | duration = float_or_none(show.get('duration_ms'), 1000) | |
75229763 S |
198 | |
199 | thumbnails = [] | |
200 | for thumbnail_key, thumbnail_url in show.items(): | |
201 | m = re.search(r'^screenshot_(?P<width>\d+)x(?P<height>\d+)$', thumbnail_key) | |
202 | if not m: | |
203 | continue | |
204 | thumbnails.append({ | |
205 | 'url': thumbnail_url, | |
206 | 'width': int(m.group('width')), | |
207 | 'height': int(m.group('height')), | |
208 | }) | |
1bf32108 S |
209 | |
210 | episode = show.get('show_TT') or show.get('show_OT') | |
211 | family = show.get('family_TT') or show.get('family_OT') | |
212 | episode_number = show.get('episode_number') | |
213 | ||
214 | title = '' | |
215 | if family: | |
216 | title += family | |
217 | if episode_number: | |
218 | title += ' #' + compat_str(episode_number) | |
219 | if episode: | |
1a1251e8 | 220 | title += ' - ' + compat_str(episode) |
1bf32108 S |
221 | |
222 | description = show.get('show_resume') or show.get('family_resume') | |
223 | ||
224 | return { | |
225 | 'id': video_id, | |
226 | 'title': title, | |
227 | 'description': description, | |
75229763 | 228 | 'thumbnails': thumbnails, |
815ac029 | 229 | 'timestamp': timestamp, |
1bf32108 S |
230 | 'uploader': uploader, |
231 | 'uploader_id': uploader_id, | |
232 | 'duration': duration, | |
233 | 'formats': formats, | |
5f6a1245 | 234 | } |