]>
Commit | Line | Data |
---|---|---|
dcdb292f | 1 | # coding: utf-8 |
1bf32108 S |
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, | |
1cc79574 PH |
11 | ) |
12 | from ..utils import ( | |
c63b3090 | 13 | clean_html, |
1cc79574 | 14 | ExtractorError, |
815ac029 S |
15 | int_or_none, |
16 | float_or_none, | |
17 | parse_iso8601, | |
4dfbf869 | 18 | parse_qs, |
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+)' |
f3bc2812 | 26 | _LOGIN_URL = 'https://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 | |
52efa4b3 | 64 | def _perform_login(self, username, password): |
ad303303 | 65 | login = self._download_json( |
e4d95865 | 66 | self._LOGIN_URL, None, 'Logging in', |
ad303303 S |
67 | data=urlencode_postdata({ |
68 | 'a': 'login', | |
69 | 'cookie': '1', | |
70 | 'username': username, | |
71 | 'password': password, | |
72 | }), | |
73 | headers={ | |
74 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', | |
75 | }) | |
c63b3090 S |
76 | |
77 | if 'erreur' in login: | |
75229763 S |
78 | raise ExtractorError('Unable to login: %s' % clean_html(login['erreur']), expected=True) |
79 | ||
2469a6ae S |
80 | @staticmethod |
81 | def _ts(): | |
82 | return int(time.time() * 1000) | |
83 | ||
cb6444e1 | 84 | def _call_api(self, path, video_id, note, sub_lang=None): |
2469a6ae | 85 | ts = compat_str(self._ts() + self._ts_offset) |
f7d159cf | 86 | tk = hashlib.md5((hashlib.md5(ts.encode('ascii')).hexdigest() + '#8S?uCraTedap6a').encode('ascii')).hexdigest() |
75229763 | 87 | url = self._API_URL_TEMPLATE % (path, ts, tk) |
cb6444e1 S |
88 | if sub_lang: |
89 | url += self._SUB_LANG_TEMPLATE % sub_lang | |
75229763 | 90 | |
2469a6ae S |
91 | request = sanitized_Request(url) |
92 | request.add_header('Referer', self._referer) | |
93 | ||
94 | resp = self._download_json(request, video_id, note) | |
75229763 S |
95 | |
96 | if isinstance(resp, dict) and resp.get('error'): | |
97 | self._raise_error(resp['error'], resp['description']) | |
98 | ||
99 | return resp | |
100 | ||
101 | def _raise_error(self, error, description): | |
102 | raise ExtractorError( | |
103 | '%s returned error: %s - %s' % (self.IE_NAME, error, description), | |
104 | expected=True) | |
c63b3090 | 105 | |
1bf32108 | 106 | def _real_extract(self, url): |
7824e1f6 | 107 | video_id = self._match_id(url) |
1bf32108 | 108 | |
2469a6ae S |
109 | # Timestamp adjustment offset between server time and local time |
110 | # must be calculated in order to use timestamps closest to server's | |
067aa17e | 111 | # in all API requests (see https://github.com/ytdl-org/youtube-dl/issues/7864) |
2469a6ae S |
112 | webpage = self._download_webpage(url, video_id) |
113 | ||
114 | player_url = self._search_regex( | |
115 | r'(["\'])(?P<player>https?://noco\.tv/(?:[^/]+/)+NocoPlayer.+?\.swf.*?)\1', | |
116 | webpage, 'noco player', group='player', | |
117 | default='http://noco.tv/cdata/js/player/NocoPlayer-v1.2.40.swf') | |
118 | ||
4dfbf869 | 119 | qs = parse_qs(player_url) |
2469a6ae S |
120 | ts = int_or_none(qs.get('ts', [None])[0]) |
121 | self._ts_offset = ts - self._ts() if ts else 0 | |
122 | self._referer = player_url | |
123 | ||
75229763 S |
124 | medias = self._call_api( |
125 | 'shows/%s/medias' % video_id, | |
126 | video_id, 'Downloading video JSON') | |
127 | ||
7212560f AD |
128 | show = self._call_api( |
129 | 'shows/by_id/%s' % video_id, | |
130 | video_id, 'Downloading show JSON')[0] | |
131 | ||
6568382d S |
132 | options = self._call_api( |
133 | 'users/init', video_id, | |
134 | 'Downloading user options JSON')['options'] | |
135 | audio_lang_pref = options.get('audio_language') or options.get('language', 'fr') | |
136 | ||
137 | if audio_lang_pref == 'original': | |
138 | audio_lang_pref = show['original_lang'] | |
7212560f | 139 | if len(medias) == 1: |
6568382d S |
140 | audio_lang_pref = list(medias.keys())[0] |
141 | elif audio_lang_pref not in medias: | |
142 | audio_lang_pref = 'fr' | |
7212560f | 143 | |
75229763 S |
144 | qualities = self._call_api( |
145 | 'qualities', | |
146 | video_id, 'Downloading qualities JSON') | |
1bf32108 S |
147 | |
148 | formats = [] | |
149 | ||
6568382d S |
150 | for audio_lang, audio_lang_dict in medias.items(): |
151 | preference = 1 if audio_lang == audio_lang_pref else 0 | |
152 | for sub_lang, lang_dict in audio_lang_dict['video_list'].items(): | |
153 | for format_id, fmt in lang_dict['quality_list'].items(): | |
154 | format_id_extended = 'audio-%s_sub-%s_%s' % (audio_lang, sub_lang, format_id) | |
155 | ||
156 | video = self._call_api( | |
157 | 'shows/%s/video/%s/%s' % (video_id, format_id.lower(), audio_lang), | |
158 | video_id, 'Downloading %s video JSON' % format_id_extended, | |
159 | sub_lang if sub_lang != 'none' else None) | |
160 | ||
161 | file_url = video['file'] | |
162 | if not file_url: | |
163 | continue | |
164 | ||
165 | if file_url in ['forbidden', 'not found']: | |
166 | popmessage = video['popmessage'] | |
167 | self._raise_error(popmessage['title'], popmessage['message']) | |
168 | ||
169 | formats.append({ | |
170 | 'url': file_url, | |
171 | 'format_id': format_id_extended, | |
815ac029 S |
172 | 'width': int_or_none(fmt.get('res_width')), |
173 | 'height': int_or_none(fmt.get('res_lines')), | |
9dc1d94a S |
174 | 'abr': int_or_none(fmt.get('audiobitrate'), 1000), |
175 | 'vbr': int_or_none(fmt.get('videobitrate'), 1000), | |
815ac029 S |
176 | 'filesize': int_or_none(fmt.get('filesize')), |
177 | 'format_note': qualities[format_id].get('quality_name'), | |
178 | 'quality': qualities[format_id].get('priority'), | |
f983b875 | 179 | 'language_preference': preference, |
6568382d | 180 | }) |
1bf32108 S |
181 | |
182 | self._sort_formats(formats) | |
183 | ||
815ac029 | 184 | timestamp = parse_iso8601(show.get('online_date_start_utc'), ' ') |
01e21b89 YCH |
185 | |
186 | if timestamp is not None and timestamp < 0: | |
187 | timestamp = None | |
188 | ||
815ac029 S |
189 | uploader = show.get('partner_name') |
190 | uploader_id = show.get('partner_key') | |
191 | duration = float_or_none(show.get('duration_ms'), 1000) | |
75229763 S |
192 | |
193 | thumbnails = [] | |
194 | for thumbnail_key, thumbnail_url in show.items(): | |
195 | m = re.search(r'^screenshot_(?P<width>\d+)x(?P<height>\d+)$', thumbnail_key) | |
196 | if not m: | |
197 | continue | |
198 | thumbnails.append({ | |
199 | 'url': thumbnail_url, | |
200 | 'width': int(m.group('width')), | |
201 | 'height': int(m.group('height')), | |
202 | }) | |
1bf32108 S |
203 | |
204 | episode = show.get('show_TT') or show.get('show_OT') | |
205 | family = show.get('family_TT') or show.get('family_OT') | |
206 | episode_number = show.get('episode_number') | |
207 | ||
208 | title = '' | |
209 | if family: | |
210 | title += family | |
211 | if episode_number: | |
212 | title += ' #' + compat_str(episode_number) | |
213 | if episode: | |
1a1251e8 | 214 | title += ' - ' + compat_str(episode) |
1bf32108 S |
215 | |
216 | description = show.get('show_resume') or show.get('family_resume') | |
217 | ||
218 | return { | |
219 | 'id': video_id, | |
220 | 'title': title, | |
221 | 'description': description, | |
75229763 | 222 | 'thumbnails': thumbnails, |
815ac029 | 223 | 'timestamp': timestamp, |
1bf32108 S |
224 | 'uploader': uploader, |
225 | 'uploader_id': uploader_id, | |
226 | 'duration': duration, | |
227 | 'formats': formats, | |
5f6a1245 | 228 | } |