[odnoklassniki] Extend _VALID_URL (closes #16081)
[youtube-dl] / youtube_dl / extractor / odnoklassniki.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 from .common import InfoExtractor
5 from ..compat import (
6     compat_etree_fromstring,
7     compat_parse_qs,
8     compat_urllib_parse_unquote,
9     compat_urllib_parse_urlparse,
10 )
11 from ..utils import (
12     ExtractorError,
13     unified_strdate,
14     int_or_none,
15     qualities,
16     unescapeHTML,
17     urlencode_postdata,
18 )
19
20
21 class OdnoklassnikiIE(InfoExtractor):
22     _VALID_URL = r'https?://(?:(?:www|m|mobile)\.)?(?:odnoklassniki|ok)\.ru/(?:video(?:embed)?/|web-api/video/moviePlayer/|live/|dk\?.*?st\.mvId=)(?P<id>[\d-]+)'
23     _TESTS = [{
24         # metadata in JSON
25         'url': 'http://ok.ru/video/20079905452',
26         'md5': '0b62089b479e06681abaaca9d204f152',
27         'info_dict': {
28             'id': '20079905452',
29             'ext': 'mp4',
30             'title': 'Культура меняет нас (прекрасный ролик!))',
31             'duration': 100,
32             'upload_date': '20141207',
33             'uploader_id': '330537914540',
34             'uploader': 'Виталий Добровольский',
35             'like_count': int,
36             'age_limit': 0,
37         },
38     }, {
39         # metadataUrl
40         'url': 'http://ok.ru/video/63567059965189-0?fromTime=5',
41         'md5': '6ff470ea2dd51d5d18c295a355b0b6bc',
42         'info_dict': {
43             'id': '63567059965189-0',
44             'ext': 'mp4',
45             'title': 'Девушка без комплексов ...',
46             'duration': 191,
47             'upload_date': '20150518',
48             'uploader_id': '534380003155',
49             'uploader': '☭ Андрей Мещанинов ☭',
50             'like_count': int,
51             'age_limit': 0,
52             'start_time': 5,
53         },
54     }, {
55         # YouTube embed (metadataUrl, provider == USER_YOUTUBE)
56         'url': 'http://ok.ru/video/64211978996595-1',
57         'md5': '2f206894ffb5dbfcce2c5a14b909eea5',
58         'info_dict': {
59             'id': 'V_VztHT5BzY',
60             'ext': 'mp4',
61             'title': 'Космическая среда от 26 августа 2015',
62             'description': 'md5:848eb8b85e5e3471a3a803dae1343ed0',
63             'duration': 440,
64             'upload_date': '20150826',
65             'uploader_id': 'tvroscosmos',
66             'uploader': 'Телестудия Роскосмоса',
67             'age_limit': 0,
68         },
69     }, {
70         # YouTube embed (metadata, provider == USER_YOUTUBE, no metadata.movie.title field)
71         'url': 'http://ok.ru/video/62036049272859-0',
72         'info_dict': {
73             'id': '62036049272859-0',
74             'ext': 'mp4',
75             'title': 'МУЗЫКА     ДОЖДЯ .',
76             'description': 'md5:6f1867132bd96e33bf53eda1091e8ed0',
77             'upload_date': '20120106',
78             'uploader_id': '473534735899',
79             'uploader': 'МARINA D',
80             'age_limit': 0,
81         },
82         'params': {
83             'skip_download': True,
84         },
85         'skip': 'Video has not been found',
86     }, {
87         'url': 'http://ok.ru/web-api/video/moviePlayer/20079905452',
88         'only_matching': True,
89     }, {
90         'url': 'http://www.ok.ru/video/20648036891',
91         'only_matching': True,
92     }, {
93         'url': 'http://www.ok.ru/videoembed/20648036891',
94         'only_matching': True,
95     }, {
96         'url': 'http://m.ok.ru/video/20079905452',
97         'only_matching': True,
98     }, {
99         'url': 'http://mobile.ok.ru/video/20079905452',
100         'only_matching': True,
101     }, {
102         'url': 'https://www.ok.ru/live/484531969818',
103         'only_matching': True,
104     }, {
105         'url': 'https://m.ok.ru/dk?st.cmd=movieLayer&st.discId=863789452017&st.retLoc=friend&st.rtu=%2Fdk%3Fst.cmd%3DfriendMovies%26st.mode%3Down%26st.mrkId%3D%257B%2522uploadedMovieMarker%2522%253A%257B%2522marker%2522%253A%25221519410114503%2522%252C%2522hasMore%2522%253Atrue%257D%252C%2522sharedMovieMarker%2522%253A%257B%2522marker%2522%253Anull%252C%2522hasMore%2522%253Afalse%257D%257D%26st.friendId%3D561722190321%26st.frwd%3Don%26_prevCmd%3DfriendMovies%26tkn%3D7257&st.discType=MOVIE&st.mvId=863789452017&_prevCmd=friendMovies&tkn=3648#lst#',
106         'only_matching': True,
107     }]
108
109     def _real_extract(self, url):
110         start_time = int_or_none(compat_parse_qs(
111             compat_urllib_parse_urlparse(url).query).get('fromTime', [None])[0])
112
113         video_id = self._match_id(url)
114
115         webpage = self._download_webpage(
116             'http://ok.ru/video/%s' % video_id, video_id)
117
118         error = self._search_regex(
119             r'[^>]+class="vp_video_stub_txt"[^>]*>([^<]+)<',
120             webpage, 'error', default=None)
121         if error:
122             raise ExtractorError(error, expected=True)
123
124         player = self._parse_json(
125             unescapeHTML(self._search_regex(
126                 r'data-options=(?P<quote>["\'])(?P<player>{.+?%s.+?})(?P=quote)' % video_id,
127                 webpage, 'player', group='player')),
128             video_id)
129
130         flashvars = player['flashvars']
131
132         metadata = flashvars.get('metadata')
133         if metadata:
134             metadata = self._parse_json(metadata, video_id)
135         else:
136             data = {}
137             st_location = flashvars.get('location')
138             if st_location:
139                 data['st.location'] = st_location
140             metadata = self._download_json(
141                 compat_urllib_parse_unquote(flashvars['metadataUrl']),
142                 video_id, 'Downloading metadata JSON',
143                 data=urlencode_postdata(data))
144
145         movie = metadata['movie']
146
147         # Some embedded videos may not contain title in movie dict (e.g.
148         # http://ok.ru/video/62036049272859-0) thus we allow missing title
149         # here and it's going to be extracted later by an extractor that
150         # will process the actual embed.
151         provider = metadata.get('provider')
152         title = movie['title'] if provider == 'UPLOADED_ODKL' else movie.get('title')
153
154         thumbnail = movie.get('poster')
155         duration = int_or_none(movie.get('duration'))
156
157         author = metadata.get('author', {})
158         uploader_id = author.get('id')
159         uploader = author.get('name')
160
161         upload_date = unified_strdate(self._html_search_meta(
162             'ya:ovs:upload_date', webpage, 'upload date', default=None))
163
164         age_limit = None
165         adult = self._html_search_meta(
166             'ya:ovs:adult', webpage, 'age limit', default=None)
167         if adult:
168             age_limit = 18 if adult == 'true' else 0
169
170         like_count = int_or_none(metadata.get('likeCount'))
171
172         info = {
173             'id': video_id,
174             'title': title,
175             'thumbnail': thumbnail,
176             'duration': duration,
177             'upload_date': upload_date,
178             'uploader': uploader,
179             'uploader_id': uploader_id,
180             'like_count': like_count,
181             'age_limit': age_limit,
182             'start_time': start_time,
183         }
184
185         if provider == 'USER_YOUTUBE':
186             info.update({
187                 '_type': 'url_transparent',
188                 'url': movie['contentId'],
189             })
190             return info
191
192         assert title
193         if provider == 'LIVE_TV_APP':
194             info['title'] = self._live_title(title)
195
196         quality = qualities(('4', '0', '1', '2', '3', '5'))
197
198         formats = [{
199             'url': f['url'],
200             'ext': 'mp4',
201             'format_id': f['name'],
202         } for f in metadata['videos']]
203
204         m3u8_url = metadata.get('hlsManifestUrl')
205         if m3u8_url:
206             formats.extend(self._extract_m3u8_formats(
207                 m3u8_url, video_id, 'mp4', 'm3u8_native',
208                 m3u8_id='hls', fatal=False))
209
210         dash_manifest = metadata.get('metadataEmbedded')
211         if dash_manifest:
212             formats.extend(self._parse_mpd_formats(
213                 compat_etree_fromstring(dash_manifest), 'mpd'))
214
215         for fmt in formats:
216             fmt_type = self._search_regex(
217                 r'\btype[/=](\d)', fmt['url'],
218                 'format type', default=None)
219             if fmt_type:
220                 fmt['quality'] = quality(fmt_type)
221
222         # Live formats
223         m3u8_url = metadata.get('hlsMasterPlaylistUrl')
224         if m3u8_url:
225             formats.extend(self._extract_m3u8_formats(
226                 m3u8_url, video_id, 'mp4', entry_protocol='m3u8',
227                 m3u8_id='hls', fatal=False))
228         rtmp_url = metadata.get('rtmpUrl')
229         if rtmp_url:
230             formats.append({
231                 'url': rtmp_url,
232                 'format_id': 'rtmp',
233                 'ext': 'flv',
234             })
235
236         self._sort_formats(formats)
237
238         info['formats'] = formats
239         return info