]> jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/videomore.py
09d12d192e3734c66be1d1113d34fec8ec88dedf
[yt-dlp.git] / yt_dlp / extractor / videomore.py
1 import re
2
3 from .common import InfoExtractor
4 from ..compat import (
5 compat_str,
6 )
7 from ..utils import (
8 int_or_none,
9 parse_qs,
10 )
11
12
13 class VideomoreBaseIE(InfoExtractor):
14 _API_BASE_URL = 'https://more.tv/api/v3/web/'
15 _VALID_URL_BASE = r'https?://(?:videomore\.ru|more\.tv)/'
16
17 def _download_page_data(self, display_id):
18 return self._download_json(
19 self._API_BASE_URL + 'PageData', display_id, query={
20 'url': '/' + display_id,
21 })['attributes']['response']['data']
22
23 def _track_url_result(self, track):
24 track_vod = track['trackVod']
25 video_url = track_vod.get('playerLink') or track_vod['link']
26 return self.url_result(
27 video_url, VideomoreIE.ie_key(), track_vod.get('hubId'))
28
29
30 class VideomoreIE(InfoExtractor):
31 IE_NAME = 'videomore'
32 _VALID_URL = r'''(?x)
33 videomore:(?P<sid>\d+)$|
34 https?://
35 (?:
36 videomore\.ru/
37 (?:
38 embed|
39 [^/]+/[^/]+
40 )/|
41 (?:
42 (?:player\.)?videomore\.ru|
43 siren\.more\.tv/player
44 )/[^/]*\?.*?\btrack_id=|
45 odysseus\.more.tv/player/(?P<partner_id>\d+)/
46 )
47 (?P<id>\d+)
48 (?:[/?#&]|\.(?:xml|json)|$)
49 '''
50 _TESTS = [{
51 'url': 'http://videomore.ru/kino_v_detalayah/5_sezon/367617',
52 'md5': '44455a346edc0d509ac5b5a5b531dc35',
53 'info_dict': {
54 'id': '367617',
55 'ext': 'flv',
56 'title': 'Кино в деталях 5 сезон В гостях Алексей Чумаков и Юлия Ковальчук',
57 'series': 'Кино в деталях',
58 'episode': 'В гостях Алексей Чумаков и Юлия Ковальчук',
59 'thumbnail': r're:^https?://.*\.jpg',
60 'duration': 2910,
61 'view_count': int,
62 'comment_count': int,
63 'age_limit': 16,
64 },
65 'skip': 'The video is not available for viewing.',
66 }, {
67 'url': 'http://videomore.ru/embed/259974',
68 'info_dict': {
69 'id': '259974',
70 'ext': 'mp4',
71 'title': 'Молодежка 2 сезон 40 серия',
72 'series': 'Молодежка',
73 'season': '2 сезон',
74 'episode': '40 серия',
75 'thumbnail': r're:^https?://.*\.jpg',
76 'duration': 2789,
77 'view_count': int,
78 'age_limit': 16,
79 },
80 'params': {
81 'skip_download': True,
82 },
83 }, {
84 'url': 'http://videomore.ru/molodezhka/sezon_promo/341073',
85 'info_dict': {
86 'id': '341073',
87 'ext': 'flv',
88 'title': 'Промо Команда проиграла из-за Бакина?',
89 'episode': 'Команда проиграла из-за Бакина?',
90 'thumbnail': r're:^https?://.*\.jpg',
91 'duration': 29,
92 'age_limit': 16,
93 'view_count': int,
94 },
95 'params': {
96 'skip_download': True,
97 },
98 'skip': 'The video is not available for viewing.',
99 }, {
100 'url': 'http://videomore.ru/elki_3?track_id=364623',
101 'only_matching': True,
102 }, {
103 'url': 'http://videomore.ru/embed/364623',
104 'only_matching': True,
105 }, {
106 'url': 'http://videomore.ru/video/tracks/364623.xml',
107 'only_matching': True,
108 }, {
109 'url': 'http://videomore.ru/video/tracks/364623.json',
110 'only_matching': True,
111 }, {
112 'url': 'http://videomore.ru/video/tracks/158031/quotes/33248',
113 'only_matching': True,
114 }, {
115 'url': 'videomore:367617',
116 'only_matching': True,
117 }, {
118 'url': 'https://player.videomore.ru/?partner_id=97&track_id=736234&autoplay=0&userToken=',
119 'only_matching': True,
120 }, {
121 'url': 'https://odysseus.more.tv/player/1788/352317',
122 'only_matching': True,
123 }, {
124 'url': 'https://siren.more.tv/player/config?track_id=352317&partner_id=1788&user_token=',
125 'only_matching': True,
126 }]
127 _GEO_BYPASS = False
128
129 @staticmethod
130 def _extract_url(webpage):
131 mobj = re.search(
132 r'<object[^>]+data=(["\'])https?://videomore\.ru/player\.swf\?.*config=(?P<url>https?://videomore\.ru/(?:[^/]+/)+\d+\.xml).*\1',
133 webpage)
134 if not mobj:
135 mobj = re.search(
136 r'<iframe[^>]+src=([\'"])(?P<url>https?://videomore\.ru/embed/\d+)',
137 webpage)
138
139 if mobj:
140 return mobj.group('url')
141
142 def _real_extract(self, url):
143 mobj = self._match_valid_url(url)
144 video_id = mobj.group('sid') or mobj.group('id')
145 partner_id = mobj.group('partner_id') or parse_qs(url).get('partner_id', [None])[0] or '97'
146
147 item = self._download_json(
148 'https://siren.more.tv/player/config', video_id, query={
149 'partner_id': partner_id,
150 'track_id': video_id,
151 })['data']['playlist']['items'][0]
152
153 title = item.get('title')
154 series = item.get('project_name')
155 season = item.get('season_name')
156 episode = item.get('episode_name')
157 if not title:
158 title = []
159 for v in (series, season, episode):
160 if v:
161 title.append(v)
162 title = ' '.join(title)
163
164 streams = item.get('streams') or []
165 for protocol in ('DASH', 'HLS'):
166 stream_url = item.get(protocol.lower() + '_url')
167 if stream_url:
168 streams.append({'protocol': protocol, 'url': stream_url})
169
170 formats = []
171 for stream in streams:
172 stream_url = stream.get('url')
173 if not stream_url:
174 continue
175 protocol = stream.get('protocol')
176 if protocol == 'DASH':
177 formats.extend(self._extract_mpd_formats(
178 stream_url, video_id, mpd_id='dash', fatal=False))
179 elif protocol == 'HLS':
180 formats.extend(self._extract_m3u8_formats(
181 stream_url, video_id, 'mp4', 'm3u8_native',
182 m3u8_id='hls', fatal=False))
183 elif protocol == 'MSS':
184 formats.extend(self._extract_ism_formats(
185 stream_url, video_id, ism_id='mss', fatal=False))
186
187 if not formats:
188 error = item.get('error')
189 if error:
190 if error in ('Данное видео недоступно для просмотра на территории этой страны', 'Данное видео доступно для просмотра только на территории России'):
191 self.raise_geo_restricted(countries=['RU'], metadata_available=True)
192 self.raise_no_formats(error, expected=True)
193 self._sort_formats(formats)
194
195 return {
196 'id': video_id,
197 'title': title,
198 'series': series,
199 'season': season,
200 'episode': episode,
201 'thumbnail': item.get('thumbnail_url'),
202 'duration': int_or_none(item.get('duration')),
203 'view_count': int_or_none(item.get('views')),
204 'age_limit': int_or_none(item.get('min_age')),
205 'formats': formats,
206 }
207
208
209 class VideomoreVideoIE(VideomoreBaseIE):
210 IE_NAME = 'videomore:video'
211 _VALID_URL = VideomoreBaseIE._VALID_URL_BASE + r'(?P<id>(?:(?:[^/]+/){2})?[^/?#&]+)(?:/*|[?#&].*?)$'
212 _TESTS = [{
213 # single video with og:video:iframe
214 'url': 'http://videomore.ru/elki_3',
215 'info_dict': {
216 'id': '364623',
217 'ext': 'flv',
218 'title': 'Ёлки 3',
219 'description': '',
220 'thumbnail': r're:^https?://.*\.jpg',
221 'duration': 5579,
222 'age_limit': 6,
223 'view_count': int,
224 },
225 'params': {
226 'skip_download': True,
227 },
228 'skip': 'Requires logging in',
229 }, {
230 # season single series with og:video:iframe
231 'url': 'http://videomore.ru/poslednii_ment/1_sezon/14_seriya',
232 'info_dict': {
233 'id': '352317',
234 'ext': 'mp4',
235 'title': 'Последний мент 1 сезон 14 серия',
236 'series': 'Последний мент',
237 'season': '1 сезон',
238 'episode': '14 серия',
239 'thumbnail': r're:^https?://.*\.jpg',
240 'duration': 2464,
241 'age_limit': 16,
242 'view_count': int,
243 },
244 'params': {
245 'skip_download': True,
246 },
247 }, {
248 'url': 'http://videomore.ru/sejchas_v_seti/serii_221-240/226_vypusk',
249 'only_matching': True,
250 }, {
251 # single video without og:video:iframe
252 'url': 'http://videomore.ru/marin_i_ego_druzya',
253 'info_dict': {
254 'id': '359073',
255 'ext': 'flv',
256 'title': '1 серия. Здравствуй, Аквавилль!',
257 'description': 'md5:c6003179538b5d353e7bcd5b1372b2d7',
258 'thumbnail': r're:^https?://.*\.jpg',
259 'duration': 754,
260 'age_limit': 6,
261 'view_count': int,
262 },
263 'params': {
264 'skip_download': True,
265 },
266 'skip': 'redirects to https://more.tv/'
267 }, {
268 'url': 'https://videomore.ru/molodezhka/6_sezon/29_seriya?utm_so',
269 'only_matching': True,
270 }, {
271 'url': 'https://more.tv/poslednii_ment/1_sezon/14_seriya',
272 'only_matching': True,
273 }]
274
275 @classmethod
276 def suitable(cls, url):
277 return False if VideomoreIE.suitable(url) else super(VideomoreVideoIE, cls).suitable(url)
278
279 def _real_extract(self, url):
280 display_id = self._match_id(url)
281 return self._track_url_result(self._download_page_data(display_id))
282
283
284 class VideomoreSeasonIE(VideomoreBaseIE):
285 IE_NAME = 'videomore:season'
286 _VALID_URL = VideomoreBaseIE._VALID_URL_BASE + r'(?!embed)(?P<id>[^/]+/[^/?#&]+)(?:/*|[?#&].*?)$'
287 _TESTS = [{
288 'url': 'http://videomore.ru/molodezhka/film_o_filme',
289 'info_dict': {
290 'id': 'molodezhka/film_o_filme',
291 'title': 'Фильм о фильме',
292 },
293 'playlist_mincount': 3,
294 }, {
295 'url': 'http://videomore.ru/molodezhka/sezon_promo?utm_so',
296 'only_matching': True,
297 }, {
298 'url': 'https://more.tv/molodezhka/film_o_filme',
299 'only_matching': True,
300 }]
301
302 @classmethod
303 def suitable(cls, url):
304 return (False if (VideomoreIE.suitable(url) or VideomoreVideoIE.suitable(url))
305 else super(VideomoreSeasonIE, cls).suitable(url))
306
307 def _real_extract(self, url):
308 display_id = self._match_id(url)
309 season = self._download_page_data(display_id)
310 season_id = compat_str(season['id'])
311 tracks = self._download_json(
312 self._API_BASE_URL + 'seasons/%s/tracks' % season_id,
313 season_id)['data']
314 entries = []
315 for track in tracks:
316 entries.append(self._track_url_result(track))
317 return self.playlist_result(entries, display_id, season.get('title'))