]> Raphaël G. Git Repositories - youtubedl/blob - youtube_dl/extractor/dplay.py
Prepare to release.
[youtubedl] / youtube_dl / extractor / dplay.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import json
5 import re
6 import time
7
8 from .common import InfoExtractor
9 from ..compat import (
10 compat_urlparse,
11 compat_HTTPError,
12 )
13 from ..utils import (
14 USER_AGENTS,
15 ExtractorError,
16 int_or_none,
17 unified_strdate,
18 remove_end,
19 update_url_query,
20 )
21
22
23 class DPlayIE(InfoExtractor):
24 _VALID_URL = r'https?://(?P<domain>www\.dplay\.(?:dk|se|no))/[^/]+/(?P<id>[^/?#]+)'
25
26 _TESTS = [{
27 # non geo restricted, via secure api, unsigned download hls URL
28 'url': 'http://www.dplay.se/nugammalt-77-handelser-som-format-sverige/season-1-svensken-lar-sig-njuta-av-livet/',
29 'info_dict': {
30 'id': '3172',
31 'display_id': 'season-1-svensken-lar-sig-njuta-av-livet',
32 'ext': 'mp4',
33 'title': 'Svensken lär sig njuta av livet',
34 'description': 'md5:d3819c9bccffd0fe458ca42451dd50d8',
35 'duration': 2650,
36 'timestamp': 1365454320,
37 'upload_date': '20130408',
38 'creator': 'Kanal 5 (Home)',
39 'series': 'Nugammalt - 77 händelser som format Sverige',
40 'season_number': 1,
41 'episode_number': 1,
42 'age_limit': 0,
43 },
44 }, {
45 # geo restricted, via secure api, unsigned download hls URL
46 'url': 'http://www.dplay.dk/mig-og-min-mor/season-6-episode-12/',
47 'info_dict': {
48 'id': '70816',
49 'display_id': 'season-6-episode-12',
50 'ext': 'mp4',
51 'title': 'Episode 12',
52 'description': 'md5:9c86e51a93f8a4401fc9641ef9894c90',
53 'duration': 2563,
54 'timestamp': 1429696800,
55 'upload_date': '20150422',
56 'creator': 'Kanal 4 (Home)',
57 'series': 'Mig og min mor',
58 'season_number': 6,
59 'episode_number': 12,
60 'age_limit': 0,
61 },
62 }, {
63 # geo restricted, via direct unsigned hls URL
64 'url': 'http://www.dplay.no/pga-tour/season-1-hoydepunkter-18-21-februar/',
65 'only_matching': True,
66 }]
67
68 def _real_extract(self, url):
69 mobj = re.match(self._VALID_URL, url)
70 display_id = mobj.group('id')
71 domain = mobj.group('domain')
72
73 webpage = self._download_webpage(url, display_id)
74
75 video_id = self._search_regex(
76 r'data-video-id=["\'](\d+)', webpage, 'video id')
77
78 info = self._download_json(
79 'http://%s/api/v2/ajax/videos?video_id=%s' % (domain, video_id),
80 video_id)['data'][0]
81
82 title = info['title']
83
84 PROTOCOLS = ('hls', 'hds')
85 formats = []
86
87 def extract_formats(protocol, manifest_url):
88 if protocol == 'hls':
89 m3u8_formats = self._extract_m3u8_formats(
90 manifest_url, video_id, ext='mp4',
91 entry_protocol='m3u8_native', m3u8_id=protocol, fatal=False)
92 # Sometimes final URLs inside m3u8 are unsigned, let's fix this
93 # ourselves. Also fragments' URLs are only served signed for
94 # Safari user agent.
95 query = compat_urlparse.parse_qs(compat_urlparse.urlparse(manifest_url).query)
96 for m3u8_format in m3u8_formats:
97 m3u8_format.update({
98 'url': update_url_query(m3u8_format['url'], query),
99 'http_headers': {
100 'User-Agent': USER_AGENTS['Safari'],
101 },
102 })
103 formats.extend(m3u8_formats)
104 elif protocol == 'hds':
105 formats.extend(self._extract_f4m_formats(
106 manifest_url + '&hdcore=3.8.0&plugin=flowplayer-3.8.0.0',
107 video_id, f4m_id=protocol, fatal=False))
108
109 domain_tld = domain.split('.')[-1]
110 if domain_tld in ('se', 'dk', 'no'):
111 for protocol in PROTOCOLS:
112 # Providing dsc-geo allows to bypass geo restriction in some cases
113 self._set_cookie(
114 'secure.dplay.%s' % domain_tld, 'dsc-geo',
115 json.dumps({
116 'countryCode': domain_tld.upper(),
117 'expiry': (time.time() + 20 * 60) * 1000,
118 }))
119 stream = self._download_json(
120 'https://secure.dplay.%s/secure/api/v2/user/authorization/stream/%s?stream_type=%s'
121 % (domain_tld, video_id, protocol), video_id,
122 'Downloading %s stream JSON' % protocol, fatal=False)
123 if stream and stream.get(protocol):
124 extract_formats(protocol, stream[protocol])
125
126 # The last resort is to try direct unsigned hls/hds URLs from info dictionary.
127 # Sometimes this does work even when secure API with dsc-geo has failed (e.g.
128 # http://www.dplay.no/pga-tour/season-1-hoydepunkter-18-21-februar/).
129 if not formats:
130 for protocol in PROTOCOLS:
131 if info.get(protocol):
132 extract_formats(protocol, info[protocol])
133
134 self._sort_formats(formats)
135
136 subtitles = {}
137 for lang in ('se', 'sv', 'da', 'nl', 'no'):
138 for format_id in ('web_vtt', 'vtt', 'srt'):
139 subtitle_url = info.get('subtitles_%s_%s' % (lang, format_id))
140 if subtitle_url:
141 subtitles.setdefault(lang, []).append({'url': subtitle_url})
142
143 return {
144 'id': video_id,
145 'display_id': display_id,
146 'title': title,
147 'description': info.get('video_metadata_longDescription'),
148 'duration': int_or_none(info.get('video_metadata_length'), scale=1000),
149 'timestamp': int_or_none(info.get('video_publish_date')),
150 'creator': info.get('video_metadata_homeChannel'),
151 'series': info.get('video_metadata_show'),
152 'season_number': int_or_none(info.get('season')),
153 'episode_number': int_or_none(info.get('episode')),
154 'age_limit': int_or_none(info.get('minimum_age')),
155 'formats': formats,
156 'subtitles': subtitles,
157 }
158
159
160 class DPlayItIE(InfoExtractor):
161 _VALID_URL = r'https?://it\.dplay\.com/[^/]+/[^/]+/(?P<id>[^/?#]+)'
162 _GEO_COUNTRIES = ['IT']
163 _TEST = {
164 'url': 'http://it.dplay.com/nove/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij/',
165 'md5': '2b808ffb00fc47b884a172ca5d13053c',
166 'info_dict': {
167 'id': '6918',
168 'display_id': 'luigi-di-maio-la-psicosi-di-stanislawskij',
169 'ext': 'mp4',
170 'title': 'Biografie imbarazzanti: Luigi Di Maio: la psicosi di Stanislawskij',
171 'description': 'md5:3c7a4303aef85868f867a26f5cc14813',
172 'thumbnail': r're:^https?://.*\.jpe?g',
173 'upload_date': '20160524',
174 'series': 'Biografie imbarazzanti',
175 'season_number': 1,
176 'episode': 'Luigi Di Maio: la psicosi di Stanislawskij',
177 'episode_number': 1,
178 },
179 }
180
181 def _real_extract(self, url):
182 display_id = self._match_id(url)
183
184 webpage = self._download_webpage(url, display_id)
185
186 info_url = self._search_regex(
187 r'url\s*:\s*["\']((?:https?:)?//[^/]+/playback/videoPlaybackInfo/\d+)',
188 webpage, 'video id')
189
190 title = remove_end(self._og_search_title(webpage), ' | Dplay')
191
192 try:
193 info = self._download_json(
194 info_url, display_id, headers={
195 'Authorization': 'Bearer %s' % self._get_cookies(url).get(
196 'dplayit_token').value,
197 'Referer': url,
198 })
199 except ExtractorError as e:
200 if isinstance(e.cause, compat_HTTPError) and e.cause.code in (400, 403):
201 info = self._parse_json(e.cause.read().decode('utf-8'), display_id)
202 error = info['errors'][0]
203 if error.get('code') == 'access.denied.geoblocked':
204 self.raise_geo_restricted(
205 msg=error.get('detail'), countries=self._GEO_COUNTRIES)
206 raise ExtractorError(info['errors'][0]['detail'], expected=True)
207 raise
208
209 hls_url = info['data']['attributes']['streaming']['hls']['url']
210
211 formats = self._extract_m3u8_formats(
212 hls_url, display_id, ext='mp4', entry_protocol='m3u8_native',
213 m3u8_id='hls')
214
215 series = self._html_search_regex(
216 r'(?s)<h1[^>]+class=["\'].*?\bshow_title\b.*?["\'][^>]*>(.+?)</h1>',
217 webpage, 'series', fatal=False)
218 episode = self._search_regex(
219 r'<p[^>]+class=["\'].*?\bdesc_ep\b.*?["\'][^>]*>\s*<br/>\s*<b>([^<]+)',
220 webpage, 'episode', fatal=False)
221
222 mobj = re.search(
223 r'(?s)<span[^>]+class=["\']dates["\'][^>]*>.+?\bS\.(?P<season_number>\d+)\s+E\.(?P<episode_number>\d+)\s*-\s*(?P<upload_date>\d{2}/\d{2}/\d{4})',
224 webpage)
225 if mobj:
226 season_number = int(mobj.group('season_number'))
227 episode_number = int(mobj.group('episode_number'))
228 upload_date = unified_strdate(mobj.group('upload_date'))
229 else:
230 season_number = episode_number = upload_date = None
231
232 return {
233 'id': info_url.rpartition('/')[-1],
234 'display_id': display_id,
235 'title': title,
236 'description': self._og_search_description(webpage),
237 'thumbnail': self._og_search_thumbnail(webpage),
238 'series': series,
239 'season_number': season_number,
240 'episode': episode,
241 'episode_number': episode_number,
242 'upload_date': upload_date,
243 'formats': formats,
244 }