Compare commits

..

3 Commits

Author SHA1 Message Date
sepro
1791d713f8 Support channel slugs 2024-11-09 05:00:42 +01:00
sepro
f09d7392a4 [ie/rutube:channel] Support main page 2024-11-09 04:21:32 +01:00
sepro
6604a447bf Replace playlist extractor 2024-11-09 04:12:43 +01:00
2 changed files with 31 additions and 49 deletions

View File

@ -1782,7 +1782,6 @@ from .rumble import (
) )
from .rutube import ( from .rutube import (
RutubeChannelIE, RutubeChannelIE,
RutubeCustomPlaylistIE,
RutubeEmbedIE, RutubeEmbedIE,
RutubeIE, RutubeIE,
RutubeMovieIE, RutubeMovieIE,

View File

@ -5,7 +5,9 @@ from ..utils import (
bool_or_none, bool_or_none,
determine_ext, determine_ext,
int_or_none, int_or_none,
js_to_json,
parse_qs, parse_qs,
str_or_none,
try_get, try_get,
unified_timestamp, unified_timestamp,
url_or_none, url_or_none,
@ -217,10 +219,6 @@ class RutubeIE(RutubeBaseIE):
'only_matching': True, 'only_matching': True,
}] }]
@classmethod
def suitable(cls, url):
return False if RutubePlaylistIE.suitable(url) else super().suitable(url)
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
query = parse_qs(url) query = parse_qs(url)
@ -376,43 +374,21 @@ class RutubePersonIE(RutubePlaylistBaseIE):
class RutubePlaylistIE(RutubePlaylistBaseIE): class RutubePlaylistIE(RutubePlaylistBaseIE):
IE_NAME = 'rutube:playlist' IE_NAME = 'rutube:playlist'
IE_DESC = 'Rutube playlists' IE_DESC = 'Rutube playlists'
_VALID_URL = r'https?://rutube\.ru/(?:video|(?:play/)?embed)/[\da-z]{32}/\?.*?\bpl_id=(?P<id>\d+)' _VALID_URL = r'https?://rutube\.ru/plst/(?P<id>\d+)'
_TESTS = [{ _TESTS = [{
'url': 'https://rutube.ru/video/cecd58ed7d531fc0f3d795d51cee9026/?pl_id=3097&pl_type=tag', 'url': 'https://rutube.ru/plst/308547/',
'info_dict': { 'info_dict': {
'id': '3097', 'id': '308547',
}, },
'playlist_count': 27, 'playlist_mincount': 22,
}, {
'url': 'https://rutube.ru/video/10b3a03fc01d5bbcc632a2f3514e8aab/?pl_id=4252&pl_type=source',
'only_matching': True,
}] }]
_PAGE_TEMPLATE = 'https://rutube.ru/api/playlist/custom/%s/videos?page=%s&format=json'
_PAGE_TEMPLATE = 'https://rutube.ru/api/playlist/%s/%s/?page=%s&format=json'
@classmethod
def suitable(cls, url):
from ..utils import int_or_none, parse_qs
if not super().suitable(url):
return False
params = parse_qs(url)
return params.get('pl_type', [None])[0] and int_or_none(params.get('pl_id', [None])[0])
def _next_page_url(self, page_num, playlist_id, item_kind):
return self._PAGE_TEMPLATE % (item_kind, playlist_id, page_num)
def _real_extract(self, url):
qs = parse_qs(url)
playlist_kind = qs['pl_type'][0]
playlist_id = qs['pl_id'][0]
return self._extract_playlist(playlist_id, item_kind=playlist_kind)
class RutubeChannelIE(RutubePlaylistBaseIE): class RutubeChannelIE(RutubePlaylistBaseIE):
IE_NAME = 'rutube:channel' IE_NAME = 'rutube:channel'
IE_DESC = 'Rutube channel' IE_DESC = 'Rutube channel'
_VALID_URL = r'https?://rutube\.ru/channel/(?P<id>\d+)/(?P<section>videos|shorts)' _VALID_URL = r'https?://rutube\.ru/(?:channel/(?P<id>\d+)|u/(?P<slug>\w+))(?:/(?P<section>videos|shorts))?'
_TESTS = [{ _TESTS = [{
'url': 'https://rutube.ru/channel/639184/videos/', 'url': 'https://rutube.ru/channel/639184/videos/',
'info_dict': { 'info_dict': {
@ -425,6 +401,18 @@ class RutubeChannelIE(RutubePlaylistBaseIE):
'id': '25902603_shorts', 'id': '25902603_shorts',
}, },
'playlist_mincount': 277, 'playlist_mincount': 277,
}, {
'url': 'https://rutube.ru/channel/25902603/',
'info_dict': {
'id': '25902603',
},
'playlist_mincount': 406,
}, {
'url': 'https://rutube.ru/u/rutube/videos/',
'info_dict': {
'id': '23704195_videos',
},
'playlist_mincount': 113,
}] }]
_PAGE_TEMPLATE = 'https://rutube.ru/api/video/person/%s/?page=%s&format=json&origin__type=%s' _PAGE_TEMPLATE = 'https://rutube.ru/api/video/person/%s/?page=%s&format=json&origin__type=%s'
@ -433,25 +421,20 @@ class RutubeChannelIE(RutubePlaylistBaseIE):
origin_type = { origin_type = {
'videos': 'rtb,rst,ifrm,rspa', 'videos': 'rtb,rst,ifrm,rspa',
'shorts': 'rshorts', 'shorts': 'rshorts',
None: '',
}.get(section) }.get(section)
return self._PAGE_TEMPLATE % (playlist_id, page_num, origin_type) return self._PAGE_TEMPLATE % (playlist_id, page_num, origin_type)
def _real_extract(self, url): def _real_extract(self, url):
playlist_id, section = self._match_valid_url(url).group('id', 'section') playlist_id, slug, section = self._match_valid_url(url).group('id', 'slug', 'section')
if slug:
webpage = self._download_webpage(url, slug)
redux_state = self._search_json(
r'window\.reduxState\s*=', webpage, 'redux state', slug, transform_source=js_to_json)
playlist_id = traverse_obj(redux_state, (
'api', 'queries', lambda k, _: k.startswith('channelIdBySlug'),
'data', 'channel_id', {int}, {str_or_none}, any))
playlist = self._extract_playlist(playlist_id, section=section) playlist = self._extract_playlist(playlist_id, section=section)
if section:
playlist['id'] = f'{playlist_id}_{section}' playlist['id'] = f'{playlist_id}_{section}'
return playlist return playlist
class RutubeCustomPlaylistIE(RutubePlaylistBaseIE):
IE_NAME = 'rutube:customplaylist'
IE_DESC = 'Rutube custom playlist'
_VALID_URL = r'https?://rutube\.ru/plst/(?P<id>\d+)'
_TESTS = [{
'url': 'https://rutube.ru/plst/308547/',
'info_dict': {
'id': '308547',
},
'playlist_mincount': 22,
}]
_PAGE_TEMPLATE = 'https://rutube.ru/api/playlist/custom/%s/videos?page=%s&format=json'