]>
Commit | Line | Data |
---|---|---|
1 | from __future__ import unicode_literals | |
2 | ||
3 | import re | |
4 | ||
5 | from .common import InfoExtractor | |
6 | from ..compat import compat_str | |
7 | from ..utils import ( | |
8 | parse_duration, | |
9 | unified_strdate, | |
10 | str_to_int, | |
11 | int_or_none, | |
12 | float_or_none, | |
13 | ISO639Utils, | |
14 | determine_ext, | |
15 | ) | |
16 | ||
17 | ||
18 | class AdobeTVBaseIE(InfoExtractor): | |
19 | _API_BASE_URL = 'http://tv.adobe.com/api/v4/' | |
20 | ||
21 | ||
22 | class AdobeTVIE(AdobeTVBaseIE): | |
23 | _VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?watch/(?P<show_urlname>[^/]+)/(?P<id>[^/]+)' | |
24 | ||
25 | _TEST = { | |
26 | 'url': 'http://tv.adobe.com/watch/the-complete-picture-with-julieanne-kost/quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop/', | |
27 | 'md5': '9bc5727bcdd55251f35ad311ca74fa1e', | |
28 | 'info_dict': { | |
29 | 'id': '10981', | |
30 | 'ext': 'mp4', | |
31 | 'title': 'Quick Tip - How to Draw a Circle Around an Object in Photoshop', | |
32 | 'description': 'md5:99ec318dc909d7ba2a1f2b038f7d2311', | |
33 | 'thumbnail': 're:https?://.*\.jpg$', | |
34 | 'upload_date': '20110914', | |
35 | 'duration': 60, | |
36 | 'view_count': int, | |
37 | }, | |
38 | } | |
39 | ||
40 | def _real_extract(self, url): | |
41 | language, show_urlname, urlname = re.match(self._VALID_URL, url).groups() | |
42 | if not language: | |
43 | language = 'en' | |
44 | ||
45 | video_data = self._download_json( | |
46 | self._API_BASE_URL + 'episode/get/?language=%s&show_urlname=%s&urlname=%s&disclosure=standard' % (language, show_urlname, urlname), | |
47 | urlname)['data'][0] | |
48 | ||
49 | formats = [{ | |
50 | 'url': source['url'], | |
51 | 'format_id': source.get('quality_level') or source['url'].split('-')[-1].split('.')[0] or None, | |
52 | 'width': int_or_none(source.get('width')), | |
53 | 'height': int_or_none(source.get('height')), | |
54 | 'tbr': int_or_none(source.get('video_data_rate')), | |
55 | } for source in video_data['videos']] | |
56 | self._sort_formats(formats) | |
57 | ||
58 | return { | |
59 | 'id': compat_str(video_data['id']), | |
60 | 'title': video_data['title'], | |
61 | 'description': video_data.get('description'), | |
62 | 'thumbnail': video_data.get('thumbnail'), | |
63 | 'upload_date': unified_strdate(video_data.get('start_date')), | |
64 | 'duration': parse_duration(video_data.get('duration')), | |
65 | 'view_count': str_to_int(video_data.get('playcount')), | |
66 | 'formats': formats, | |
67 | } | |
68 | ||
69 | ||
70 | class AdobeTVPlaylistBaseIE(AdobeTVBaseIE): | |
71 | def _parse_page_data(self, page_data): | |
72 | return [self.url_result(self._get_element_url(element_data)) for element_data in page_data] | |
73 | ||
74 | def _extract_playlist_entries(self, url, display_id): | |
75 | page = self._download_json(url, display_id) | |
76 | entries = self._parse_page_data(page['data']) | |
77 | for page_num in range(2, page['paging']['pages'] + 1): | |
78 | entries.extend(self._parse_page_data( | |
79 | self._download_json(url + '&page=%d' % page_num, display_id)['data'])) | |
80 | return entries | |
81 | ||
82 | ||
83 | class AdobeTVShowIE(AdobeTVPlaylistBaseIE): | |
84 | _VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?show/(?P<id>[^/]+)' | |
85 | ||
86 | _TEST = { | |
87 | 'url': 'http://tv.adobe.com/show/the-complete-picture-with-julieanne-kost', | |
88 | 'info_dict': { | |
89 | 'id': '36', | |
90 | 'title': 'The Complete Picture with Julieanne Kost', | |
91 | 'description': 'md5:fa50867102dcd1aa0ddf2ab039311b27', | |
92 | }, | |
93 | 'playlist_mincount': 136, | |
94 | } | |
95 | ||
96 | def _get_element_url(self, element_data): | |
97 | return element_data['urls'][0] | |
98 | ||
99 | def _real_extract(self, url): | |
100 | language, show_urlname = re.match(self._VALID_URL, url).groups() | |
101 | if not language: | |
102 | language = 'en' | |
103 | query = 'language=%s&show_urlname=%s' % (language, show_urlname) | |
104 | ||
105 | show_data = self._download_json(self._API_BASE_URL + 'show/get/?%s' % query, show_urlname)['data'][0] | |
106 | ||
107 | return self.playlist_result( | |
108 | self._extract_playlist_entries(self._API_BASE_URL + 'episode/?%s' % query, show_urlname), | |
109 | compat_str(show_data['id']), | |
110 | show_data['show_name'], | |
111 | show_data['show_description']) | |
112 | ||
113 | ||
114 | class AdobeTVChannelIE(AdobeTVPlaylistBaseIE): | |
115 | _VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?channel/(?P<id>[^/]+)(?:/(?P<category_urlname>[^/]+))?' | |
116 | ||
117 | _TEST = { | |
118 | 'url': 'http://tv.adobe.com/channel/development', | |
119 | 'info_dict': { | |
120 | 'id': 'development', | |
121 | }, | |
122 | 'playlist_mincount': 96, | |
123 | } | |
124 | ||
125 | def _get_element_url(self, element_data): | |
126 | return element_data['url'] | |
127 | ||
128 | def _real_extract(self, url): | |
129 | language, channel_urlname, category_urlname = re.match(self._VALID_URL, url).groups() | |
130 | if not language: | |
131 | language = 'en' | |
132 | query = 'language=%s&channel_urlname=%s' % (language, channel_urlname) | |
133 | if category_urlname: | |
134 | query += '&category_urlname=%s' % category_urlname | |
135 | ||
136 | return self.playlist_result( | |
137 | self._extract_playlist_entries(self._API_BASE_URL + 'show/?%s' % query, channel_urlname), | |
138 | channel_urlname) | |
139 | ||
140 | ||
141 | class AdobeTVVideoIE(InfoExtractor): | |
142 | _VALID_URL = r'https?://video\.tv\.adobe\.com/v/(?P<id>\d+)' | |
143 | ||
144 | _TEST = { | |
145 | # From https://helpx.adobe.com/acrobat/how-to/new-experience-acrobat-dc.html?set=acrobat--get-started--essential-beginners | |
146 | 'url': 'https://video.tv.adobe.com/v/2456/', | |
147 | 'md5': '43662b577c018ad707a63766462b1e87', | |
148 | 'info_dict': { | |
149 | 'id': '2456', | |
150 | 'ext': 'mp4', | |
151 | 'title': 'New experience with Acrobat DC', | |
152 | 'description': 'New experience with Acrobat DC', | |
153 | 'duration': 248.667, | |
154 | }, | |
155 | } | |
156 | ||
157 | def _real_extract(self, url): | |
158 | video_id = self._match_id(url) | |
159 | video_data = self._download_json(url + '?format=json', video_id) | |
160 | ||
161 | formats = [{ | |
162 | 'format_id': '%s-%s' % (determine_ext(source['src']), source.get('height')), | |
163 | 'url': source['src'], | |
164 | 'width': int_or_none(source.get('width')), | |
165 | 'height': int_or_none(source.get('height')), | |
166 | 'tbr': int_or_none(source.get('bitrate')), | |
167 | } for source in video_data['sources']] | |
168 | self._sort_formats(formats) | |
169 | ||
170 | # For both metadata and downloaded files the duration varies among | |
171 | # formats. I just pick the max one | |
172 | duration = max(filter(None, [ | |
173 | float_or_none(source.get('duration'), scale=1000) | |
174 | for source in video_data['sources']])) | |
175 | ||
176 | subtitles = {} | |
177 | for translation in video_data.get('translations', []): | |
178 | lang_id = translation.get('language_w3c') or ISO639Utils.long2short(translation['language_medium']) | |
179 | if lang_id not in subtitles: | |
180 | subtitles[lang_id] = [] | |
181 | subtitles[lang_id].append({ | |
182 | 'url': translation['vttPath'], | |
183 | 'ext': 'vtt', | |
184 | }) | |
185 | ||
186 | return { | |
187 | 'id': video_id, | |
188 | 'formats': formats, | |
189 | 'title': video_data['title'], | |
190 | 'description': video_data.get('description'), | |
191 | 'thumbnail': video_data['video'].get('poster'), | |
192 | 'duration': duration, | |
193 | 'subtitles': subtitles, | |
194 | } |