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