2 from __future__ 
import unicode_literals
 
   7 from .common 
import InfoExtractor
 
  11     compat_urllib_parse_urlparse
, 
  23 class RutubeBaseIE(InfoExtractor
): 
  24     def _download_api_info(self
, video_id
, query
=None): 
  27         query
['format'] = 'json' 
  28         return self
._download
_json
( 
  29             'http://rutube.ru/api/video/%s/' % video_id
, 
  30             video_id
, 'Downloading video JSON', 
  31             'Unable to download video JSON', query
=query
) 
  34     def _extract_info(video
, video_id
=None, require_title
=True): 
  35         title 
= video
['title'] if require_title 
else video
.get('title') 
  37         age_limit 
= video
.get('is_adult') 
  38         if age_limit 
is not None: 
  39             age_limit 
= 18 if age_limit 
is True else 0 
  41         uploader_id 
= try_get(video
, lambda x
: x
['author']['id']) 
  42         category 
= try_get(video
, lambda x
: x
['category']['name']) 
  45             'id': video
.get('id') or video_id 
if video_id 
else video
['id'], 
  47             'description': video
.get('description'), 
  48             'thumbnail': video
.get('thumbnail_url'), 
  49             'duration': int_or_none(video
.get('duration')), 
  50             'uploader': try_get(video
, lambda x
: x
['author']['name']), 
  51             'uploader_id': compat_str(uploader_id
) if uploader_id 
else None, 
  52             'timestamp': unified_timestamp(video
.get('created_ts')), 
  53             'category': [category
] if category 
else None, 
  54             'age_limit': age_limit
, 
  55             'view_count': int_or_none(video
.get('hits')), 
  56             'comment_count': int_or_none(video
.get('comments_count')), 
  57             'is_live': bool_or_none(video
.get('is_livestream')), 
  60     def _download_and_extract_info(self
, video_id
, query
=None): 
  61         return self
._extract
_info
( 
  62             self
._download
_api
_info
(video_id
, query
=query
), video_id
) 
  64     def _download_api_options(self
, video_id
, query
=None): 
  67         query
['format'] = 'json' 
  68         return self
._download
_json
( 
  69             'http://rutube.ru/api/play/options/%s/' % video_id
, 
  70             video_id
, 'Downloading options JSON', 
  71             'Unable to download options JSON', 
  72             headers
=self
.geo_verification_headers(), query
=query
) 
  74     def _extract_formats(self
, options
, video_id
): 
  76         for format_id
, format_url 
in options
['video_balancer'].items(): 
  77             ext 
= determine_ext(format_url
) 
  79                 formats
.extend(self
._extract
_m
3u8_formats
( 
  80                     format_url
, video_id
, 'mp4', m3u8_id
=format_id
, fatal
=False)) 
  82                 formats
.extend(self
._extract
_f
4m
_formats
( 
  83                     format_url
, video_id
, f4m_id
=format_id
, fatal
=False)) 
  87                     'format_id': format_id
, 
  89         self
._sort
_formats
(formats
) 
  92     def _download_and_extract_formats(self
, video_id
, query
=None): 
  93         return self
._extract
_formats
( 
  94             self
._download
_api
_options
(video_id
, query
=query
), video_id
) 
  97 class RutubeIE(RutubeBaseIE
): 
  99     IE_DESC 
= 'Rutube videos' 
 100     _VALID_URL 
= r
'https?://rutube\.ru/(?:video|(?:play/)?embed)/(?P<id>[\da-z]{32})' 
 103         'url': 'http://rutube.ru/video/3eac3b4561676c17df9132a9a1e62e3e/', 
 104         'md5': '1d24f180fac7a02f3900712e5a5764d6', 
 106             'id': '3eac3b4561676c17df9132a9a1e62e3e', 
 108             'title': 'Раненный кенгуру забежал в аптеку', 
 109             'description': 'http://www.ntdtv.ru ', 
 111             'uploader': 'NTDRussian', 
 112             'uploader_id': '29790', 
 113             'timestamp': 1381943602, 
 114             'upload_date': '20131016', 
 118         'url': 'http://rutube.ru/play/embed/a10e53b86e8f349080f718582ce4c661', 
 119         'only_matching': True, 
 121         'url': 'http://rutube.ru/embed/a10e53b86e8f349080f718582ce4c661', 
 122         'only_matching': True, 
 124         'url': 'http://rutube.ru/video/3eac3b4561676c17df9132a9a1e62e3e/?pl_id=4252', 
 125         'only_matching': True, 
 127         'url': 'https://rutube.ru/video/10b3a03fc01d5bbcc632a2f3514e8aab/?pl_type=source', 
 128         'only_matching': True, 
 132     def suitable(cls
, url
): 
 133         return False if RutubePlaylistIE
.suitable(url
) else super(RutubeIE
, cls
).suitable(url
) 
 136     def _extract_urls(webpage
): 
 137         return [mobj
.group('url') for mobj 
in re
.finditer( 
 138             r
'<iframe[^>]+?src=(["\'])(?P
<url
>(?
:https?
:)?
//rutube\
.ru
/embed
/[\da
-z
]{32}
.*?
)\
1', 
 141     def _real_extract(self, url): 
 142         video_id = self._match_id(url) 
 143         info = self._download_and_extract_info(video_id) 
 144         info['formats
'] = self._download_and_extract_formats(video_id) 
 148 class RutubeEmbedIE(RutubeBaseIE): 
 149     IE_NAME = 'rutube
:embed
' 
 150     IE_DESC = 'Rutube embedded videos
' 
 151     _VALID_URL = r'https?
://rutube\
.ru
/(?
:video|play
)/embed
/(?P
<id>[0-9]+)' 
 154         'url
': 'http
://rutube
.ru
/video
/embed
/6722881?vk_puid37
=&vk_puid38
=', 
 156             'id': 'a10e53b86e8f349080f718582ce4c661
', 
 158             'timestamp
': 1387830582, 
 159             'upload_date
': '20131223', 
 160             'uploader_id
': '297833', 
 161             'description
': 'Видео группы ★http
://vk
.com
/foxkidsreset★ музей Fox Kids и Jetix
<br
/><br
/> восстановлено и сделано в шикоформате subziro89 http
://vk
.com
/subziro89
', 
 162             'uploader
': 'subziro89 ILya
', 
 163             'title
': 'Мистический городок Эйри в Индиан 
5 серия озвучка subziro89
', 
 166             'skip_download
': True, 
 169         'url
': 'http
://rutube
.ru
/play
/embed
/8083783', 
 170         'only_matching
': True, 
 173         'url
': 'https
://rutube
.ru
/play
/embed
/10631925?p
=IbAigKqWd1do4mjaM5XLIQ
', 
 174         'only_matching
': True, 
 177     def _real_extract(self, url): 
 178         embed_id = self._match_id(url) 
 179         # Query may contain private videos token and should be passed to API 
 180         # requests (see #19163) 
 181         query = compat_parse_qs(compat_urllib_parse_urlparse(url).query) 
 182         options = self._download_api_options(embed_id, query) 
 183         video_id = options['effective_video
'] 
 184         formats = self._extract_formats(options, video_id) 
 185         info = self._download_and_extract_info(video_id, query) 
 187             'extractor_key
': 'Rutube
', 
 193 class RutubePlaylistBaseIE(RutubeBaseIE): 
 194     def _next_page_url(self, page_num, playlist_id, *args, **kwargs): 
 195         return self._PAGE_TEMPLATE % (playlist_id, page_num) 
 197     def _entries(self, playlist_id, *args, **kwargs): 
 199         for pagenum in itertools.count(1): 
 200             page = self._download_json( 
 201                 next_page_url or self._next_page_url( 
 202                     pagenum, playlist_id, *args, **kwargs), 
 203                 playlist_id, 'Downloading page 
%s' % pagenum) 
 205             results = page.get('results
') 
 206             if not results or not isinstance(results, list): 
 209             for result in results: 
 210                 video_url = url_or_none(result.get('video_url
')) 
 213                 entry = self._extract_info(result, require_title=False) 
 217                     'ie_key
': RutubeIE.ie_key(), 
 221             next_page_url = page.get('next
') 
 222             if not next_page_url or not page.get('has_next
'): 
 225     def _extract_playlist(self, playlist_id, *args, **kwargs): 
 226         return self.playlist_result( 
 227             self._entries(playlist_id, *args, **kwargs), 
 228             playlist_id, kwargs.get('playlist_name
')) 
 230     def _real_extract(self, url): 
 231         return self._extract_playlist(self._match_id(url)) 
 234 class RutubeChannelIE(RutubePlaylistBaseIE): 
 235     IE_NAME = 'rutube
:channel
' 
 236     IE_DESC = 'Rutube channels
' 
 237     _VALID_URL = r'https?
://rutube\
.ru
/tags
/video
/(?P
<id>\d
+)' 
 239         'url
': 'http
://rutube
.ru
/tags
/video
/1800/', 
 243         'playlist_mincount
': 68, 
 246     _PAGE_TEMPLATE = 'http
://rutube
.ru
/api
/tags
/video
/%s/?page
=%s&format
=json
' 
 249 class RutubeMovieIE(RutubePlaylistBaseIE): 
 250     IE_NAME = 'rutube
:movie
' 
 251     IE_DESC = 'Rutube movies
' 
 252     _VALID_URL = r'https?
://rutube\
.ru
/metainfo
/tv
/(?P
<id>\d
+)' 
 255     _MOVIE_TEMPLATE = 'http
://rutube
.ru
/api
/metainfo
/tv
/%s/?format
=json
' 
 256     _PAGE_TEMPLATE = 'http
://rutube
.ru
/api
/metainfo
/tv
/%s/video?page
=%s&format
=json
' 
 258     def _real_extract(self, url): 
 259         movie_id = self._match_id(url) 
 260         movie = self._download_json( 
 261             self._MOVIE_TEMPLATE % movie_id, movie_id, 
 262             'Downloading movie JSON
') 
 263         return self._extract_playlist( 
 264             movie_id, playlist_name=movie.get('name
')) 
 267 class RutubePersonIE(RutubePlaylistBaseIE): 
 268     IE_NAME = 'rutube
:person
' 
 269     IE_DESC = 'Rutube person videos
' 
 270     _VALID_URL = r'https?
://rutube\
.ru
/video
/person
/(?P
<id>\d
+)' 
 272         'url
': 'http
://rutube
.ru
/video
/person
/313878/', 
 276         'playlist_mincount
': 37, 
 279     _PAGE_TEMPLATE = 'http
://rutube
.ru
/api
/video
/person
/%s/?page
=%s&format
=json
' 
 282 class RutubePlaylistIE(RutubePlaylistBaseIE): 
 283     IE_NAME = 'rutube
:playlist
' 
 284     IE_DESC = 'Rutube playlists
' 
 285     _VALID_URL = r'https?
://rutube\
.ru
/(?
:video|
(?
:play
/)?embed
)/[\da
-z
]{32}
/\?.*?
\bpl
_id
=(?P
<id>\d
+)' 
 287         'url
': 'https
://rutube
.ru
/video
/cecd58ed7d531fc0f3d795d51cee9026
/?pl_id
=3097&pl_type
=tag
', 
 291         'playlist_count
': 27, 
 293         'url
': 'https
://rutube
.ru
/video
/10b3a03fc01d5bbcc632a2f3514e8aab
/?pl_id
=4252&pl_type
=source
', 
 294         'only_matching
': True, 
 297     _PAGE_TEMPLATE = 'http
://rutube
.ru
/api
/playlist
/%s/%s/?page
=%s&format
=json
' 
 300     def suitable(cls, url): 
 301         if not super(RutubePlaylistIE, cls).suitable(url): 
 303         params = compat_parse_qs(compat_urllib_parse_urlparse(url).query) 
 304         return params.get('pl_type
', [None])[0] and int_or_none(params.get('pl_id
', [None])[0]) 
 306     def _next_page_url(self, page_num, playlist_id, item_kind): 
 307         return self._PAGE_TEMPLATE % (item_kind, playlist_id, page_num) 
 309     def _real_extract(self, url): 
 310         qs = compat_parse_qs(compat_urllib_parse_urlparse(url).query) 
 311         playlist_kind = qs['pl_type
'][0] 
 312         playlist_id = qs['pl_id
'][0] 
 313         return self._extract_playlist(playlist_id, item_kind=playlist_kind)