Revert "[sauceplus]support for sauceplus via floatplane extractor"

This reverts commit 3edc695b1b.
pull/14053/head
i3p9 1 day ago
parent 3edc695b1b
commit f9ac46999e

@ -657,8 +657,6 @@ from .flickr import FlickrIE
from .floatplane import (
FloatplaneChannelIE,
FloatplaneIE,
SauceplusChannelIE,
SauceplusIE,
)
from .folketinget import FolketingetIE
from .footyroom import FootyRoomIE

@ -22,33 +22,27 @@ class FloatplaneBaseIE(InfoExtractor):
post_id = self._match_id(url)
post_data = self._download_json(
f'{self._BASE_URL}/api/v3/content/post',
post_id,
query={'id': post_id},
note='Downloading post data',
errnote='Unable to download post data',
impersonate=self._IMPERSONATE_TARGET,
)
f'{self._BASE_URL}/api/v3/content/post', post_id, query={'id': post_id},
note='Downloading post data', errnote='Unable to download post data',
impersonate=self._IMPERSONATE_TARGET)
if not any(traverse_obj(post_data, ('metadata', ('hasVideo', 'hasAudio')))):
raise ExtractorError('Post does not contain a video or audio track', expected=True)
uploader_url = format_field(post_data, [('creator', 'urlname')], f'{self._BASE_URL}/channel/%s/home') or None
uploader_url = format_field(
post_data, [('creator', 'urlname')], f'{self._BASE_URL}/channel/%s/home') or None
common_info = {
'uploader_url': uploader_url,
'channel_url': urljoin(f'{uploader_url}/', traverse_obj(post_data, ('channel', 'urlname'))),
'availability': self._availability(needs_subscription=True),
**traverse_obj(
post_data,
{
**traverse_obj(post_data, {
'uploader': ('creator', 'title', {str}),
'uploader_id': ('creator', 'id', {str}),
'channel': ('channel', 'title', {str}),
'channel_id': ('channel', 'id', {str}),
'release_timestamp': ('releaseDate', {parse_iso8601}),
},
),
}),
}
items = []
@ -57,23 +51,15 @@ class FloatplaneBaseIE(InfoExtractor):
media_typ = media.get('type') or 'video'
metadata = self._download_json(
f'{self._BASE_URL}/api/v3/content/{media_typ}',
media_id,
query={'id': media_id},
note=f'Downloading {media_typ} metadata',
impersonate=self._IMPERSONATE_TARGET,
)
f'{self._BASE_URL}/api/v3/content/{media_typ}', media_id, query={'id': media_id},
note=f'Downloading {media_typ} metadata', impersonate=self._IMPERSONATE_TARGET)
stream = self._download_json(
f'{self._BASE_URL}/api/v2/cdn/delivery',
media_id,
query={
f'{self._BASE_URL}/api/v2/cdn/delivery', media_id, query={
'type': 'vod' if media_typ == 'video' else 'aod',
'guid': metadata['guid'],
},
note=f'Downloading {media_typ} stream data',
impersonate=self._IMPERSONATE_TARGET,
)
}, note=f'Downloading {media_typ} stream data',
impersonate=self._IMPERSONATE_TARGET)
path_template = traverse_obj(stream, ('resource', 'uri', {str}))
@ -85,12 +71,8 @@ class FloatplaneBaseIE(InfoExtractor):
formats = []
for quality in traverse_obj(stream, ('resource', 'data', 'qualityLevels', ...)):
url = urljoin(
stream['cdn'],
format_path(
traverse_obj(stream, ('resource', 'data', 'qualityLevelParams', quality['name'], {dict})),
),
)
url = urljoin(stream['cdn'], format_path(traverse_obj(
stream, ('resource', 'data', 'qualityLevelParams', quality['name'], {dict}))))
format_id = traverse_obj(quality, ('name', {str}))
hls_aes = {}
m3u8_data = None
@ -98,82 +80,59 @@ class FloatplaneBaseIE(InfoExtractor):
# If we need impersonation for the API, then we need it for HLS keys too: extract in advance
if self._IMPERSONATE_TARGET is not None:
m3u8_data = self._download_webpage(
url,
media_id,
fatal=False,
impersonate=self._IMPERSONATE_TARGET,
headers=self._HEADERS,
url, media_id, fatal=False, impersonate=self._IMPERSONATE_TARGET, headers=self._HEADERS,
note=join_nonempty('Downloading', format_id, 'm3u8 information', delim=' '),
errnote=join_nonempty('Failed to download', format_id, 'm3u8 information', delim=' '),
)
errnote=join_nonempty('Failed to download', format_id, 'm3u8 information', delim=' '))
if not m3u8_data:
continue
key_url = self._search_regex(
r'#EXT-X-KEY:METHOD=AES-128,URI="(https?://[^"]+)"', m3u8_data, 'HLS AES key URI', default=None,
)
r'#EXT-X-KEY:METHOD=AES-128,URI="(https?://[^"]+)"',
m3u8_data, 'HLS AES key URI', default=None)
if key_url:
urlh = self._request_webpage(
key_url,
media_id,
fatal=False,
impersonate=self._IMPERSONATE_TARGET,
headers=self._HEADERS,
key_url, media_id, fatal=False, impersonate=self._IMPERSONATE_TARGET, headers=self._HEADERS,
note=join_nonempty('Downloading', format_id, 'HLS AES key', delim=' '),
errnote=join_nonempty('Failed to download', format_id, 'HLS AES key', delim=' '),
)
errnote=join_nonempty('Failed to download', format_id, 'HLS AES key', delim=' '))
if urlh:
hls_aes['key'] = urlh.read().hex()
formats.append(
{
**traverse_obj(
quality,
{
formats.append({
**traverse_obj(quality, {
'format_note': ('label', {str}),
'width': ('width', {int}),
'height': ('height', {int}),
},
),
}),
**parse_codecs(quality.get('codecs')),
'url': url,
'ext': determine_ext(url.partition('/chunk.m3u8')[0], 'mp4'),
'format_id': format_id,
'hls_media_playlist_data': m3u8_data,
'hls_aes': hls_aes or None,
},
)
items.append(
{
})
items.append({
**common_info,
'id': media_id,
**traverse_obj(
metadata,
{
**traverse_obj(metadata, {
'title': ('title', {str}),
'duration': ('duration', {int_or_none}),
'thumbnail': ('thumbnail', 'path', {url_or_none}),
},
),
}),
'formats': formats,
},
)
})
post_info = {
**common_info,
'id': post_id,
'display_id': post_id,
**traverse_obj(
post_data,
{
**traverse_obj(post_data, {
'title': ('title', {str}),
'description': ('text', {clean_html}),
'like_count': ('likes', {int_or_none}),
'dislike_count': ('dislikes', {int_or_none}),
'comment_count': ('comments', {int_or_none}),
'thumbnail': ('thumbnail', 'path', {url_or_none}),
},
),
}),
'http_headers': self._HEADERS,
}
@ -192,8 +151,7 @@ class FloatplaneIE(FloatplaneBaseIE):
'Origin': _BASE_URL,
'Referer': f'{_BASE_URL}/',
}
_TESTS = [
{
_TESTS = [{
'url': 'https://www.floatplane.com/post/2Yf3UedF7C',
'info_dict': {
'id': 'yuleLogLTT',
@ -217,9 +175,7 @@ class FloatplaneIE(FloatplaneBaseIE):
'availability': 'subscriber_only',
},
'params': {'skip_download': 'm3u8'},
'skip': 'requires LTT subscription',
},
{
}, {
'url': 'https://www.floatplane.com/post/j2jqG3JmgJ',
'info_dict': {
'id': 'j2jqG3JmgJ',
@ -240,8 +196,7 @@ class FloatplaneIE(FloatplaneBaseIE):
'availability': 'subscriber_only',
},
'playlist_count': 2,
},
{
}, {
'url': 'https://www.floatplane.com/post/3tK2tInhoN',
'info_dict': {
'id': '3tK2tInhoN',
@ -262,8 +217,7 @@ class FloatplaneIE(FloatplaneBaseIE):
'availability': 'subscriber_only',
},
'playlist_count': 2,
},
{
}, {
'url': 'https://beta.floatplane.com/post/d870PEFXS1',
'info_dict': {
'id': 'bg9SuYKEww',
@ -287,12 +241,11 @@ class FloatplaneIE(FloatplaneBaseIE):
'availability': 'subscriber_only',
},
'params': {'skip_download': 'm3u8'},
},
{
}, {
'url': 'https://www.floatplane.com/post/65B5PNoBtf',
'info_dict': {
'id': '65B5PNoBtf',
'description': "I recorded the inbuilt demo mode for your 90's enjoyment, thanks for being Floaties!",
'description': 'I recorded the inbuilt demo mode for your 90\'s enjoyment, thanks for being Floaties!',
'display_id': '65B5PNoBtf',
'like_count': int,
'release_timestamp': 1701249480,
@ -310,8 +263,7 @@ class FloatplaneIE(FloatplaneBaseIE):
'release_date': '20231129',
},
'playlist_count': 2,
'playlist': [
{
'playlist': [{
'info_dict': {
'id': 'ISPJjexylS',
'ext': 'mp4',
@ -328,8 +280,7 @@ class FloatplaneIE(FloatplaneBaseIE):
'channel_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home/thedrumthing',
'uploader_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home',
},
},
{
}, {
'info_dict': {
'id': 'qKfxu6fEpu',
'ext': 'aac',
@ -345,161 +296,20 @@ class FloatplaneIE(FloatplaneBaseIE):
'channel_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home/thedrumthing',
'uploader_url': 'https://www.floatplane.com/channel/TheTrashNetwork/home',
},
},
],
}],
'skip': 'requires subscription: "The Trash Network"',
'params': {'skip_download': 'm3u8'},
},
]
}]
def _real_initialize(self):
if not self._get_cookies(self._BASE_URL).get('sails.sid'):
self.raise_login_required()
class SauceplusIE(FloatplaneBaseIE):
_VALID_URL = r'https?://(?:(?:www|beta)\.)?sauceplus\.com/post/(?P<id>\w+)'
_BASE_URL = 'https://www.sauceplus.com'
_IMPERSONATE_TARGET = None
_HEADERS = {
'Origin': _BASE_URL,
'Referer': f'{_BASE_URL}/',
}
_TESTS = [
{
'url': 'https://www.sauceplus.com/post/RMRau1tqaU',
'info_dict': {
'id': 'EbcXSMMRFc',
'ext': 'mp4',
'display_id': 'RMRau1tqaU',
'title': 'Scare the Coyote - Episode 1',
'description': 'Scare the Coyote airs Thursdays only on Sauce Plus',
'thumbnail': r're:^https?://.*\.jpe?g$',
'duration': 2676,
'comment_count': int,
'like_count': int,
'dislike_count': int,
'release_date': '20250616',
'release_timestamp': 1750083240,
'uploader': 'Scare The Coyote',
'uploader_id': '683e0a3269688656a5a49a44',
'uploader_url': 'https://www.sauceplus.com/channel/ScareTheCoyote/home',
'channel': 'Scare The Coyote',
'channel_id': '683e0a326968866ceba49a45',
'channel_url': 'https://www.sauceplus.com/channel/ScareTheCoyote/home/main',
'availability': 'subscriber_only',
},
'params': {'skip_download': 'm3u8'},
'skip': 'requires subscription: Sauceplus',
},
{
'url': 'https://www.sauceplus.com/post/W9drFVKOwh',
'info_dict': {
'id': '0GZy0KBdeQ',
'ext': 'mp4',
'display_id': 'W9drFVKOwh',
'title': 'BYSM_00190_F.mp4',
'description': '',
'thumbnail': r're:^https?://.*\.jpe?g$',
'duration': 158,
'comment_count': int,
'like_count': int,
'dislike_count': int,
'release_date': '20241207',
'release_timestamp': 1733542704,
'uploader': 'The Backyard Scientist',
'uploader_id': '6848a6ec7e317363389190b9',
'uploader_url': 'https://www.sauceplus.com/channel/TheBackyardScientist/home',
'channel': 'The Backyard Scientist',
'channel_id': '6848a6ec7e31733d359190ba',
'channel_url': 'https://www.sauceplus.com/channel/TheBackyardScientist/home/main',
'availability': 'subscriber_only',
},
'params': {'skip_download': 'm3u8'},
'skip': 'requires subscription: Sauceplus',
},
]
def _real_initialize(self):
if not self._get_cookies(self._BASE_URL).get('__Host-sp-sess'):
self.raise_login_required()
class SauceplusChannelIE(InfoExtractor):
_VALID_URL = r'https?://(?:(?:www|beta)\.)?sauceplus\.com/channel/(?P<id>[\w-]+)/home(?:/(?P<channel>[\w-]+))?'
_PAGE_SIZE = 20
_TESTS = [
{
'url': 'https://www.sauceplus.com/channel/ScareTheCoyote/home',
'info_dict': {
'id': 'ScareTheCoyote',
'title': 'Scare The Coyote',
},
'playlist_mincount': 7,
'skip': 'requires subscription: Scare The Coyote',
},
{
'url': 'https://www.sauceplus.com/channel/SafetyThird/home',
'info_dict': {
'id': 'SafetyThird',
'title': 'Safety Third',
},
'playlist_mincount': 150,
'skip': 'requires subscription: Safety Third',
},
]
def _fetch_page(self, display_id, creator_id, channel_id, page):
query = {
'id': creator_id,
'limit': self._PAGE_SIZE,
'fetchAfter': page * self._PAGE_SIZE,
}
if channel_id:
query['channel'] = channel_id
page_data = self._download_json(
'https://www.sauceplus.com/api/v3/content/creator',
display_id,
query=query,
note=f'Downloading page {page + 1}',
)
for post in page_data or []:
yield self.url_result(
f"https://www.sauceplus.com/post/{post['id']}",
SauceplusIE,
id=post['id'],
title=post.get('title'),
release_timestamp=parse_iso8601(post.get('releaseDate')),
)
def _real_extract(self, url):
creator, channel = self._match_valid_url(url).group('id', 'channel')
display_id = join_nonempty(creator, channel, delim='/')
creator_data = self._download_json(
'https://www.sauceplus.com/api/v3/creator/named', display_id, query={'creatorURL[0]': creator},
)[0]
channel_data = (
traverse_obj(creator_data, ('channels', lambda _, v: v['urlname'] == channel), get_all=False) or {}
)
return self.playlist_result(
OnDemandPagedList(
functools.partial(self._fetch_page, display_id, creator_data['id'], channel_data.get('id')),
self._PAGE_SIZE,
),
display_id,
title=channel_data.get('title') or creator_data.get('title'),
description=channel_data.get('about') or creator_data.get('about'),
)
class FloatplaneChannelIE(InfoExtractor):
_VALID_URL = r'https?://(?:(?:www|beta)\.)?floatplane\.com/channel/(?P<id>[\w-]+)/home(?:/(?P<channel>[\w-]+))?'
_PAGE_SIZE = 20
_TESTS = [
{
_TESTS = [{
'url': 'https://www.floatplane.com/channel/linustechtips/home/ltxexpo',
'info_dict': {
'id': 'linustechtips/ltxexpo',
@ -507,8 +317,7 @@ class FloatplaneChannelIE(InfoExtractor):
'description': 'md5:9819002f9ebe7fd7c75a3a1d38a59149',
},
'playlist_mincount': 51,
},
{
}, {
'url': 'https://www.floatplane.com/channel/ShankMods/home',
'info_dict': {
'id': 'ShankMods',
@ -516,8 +325,7 @@ class FloatplaneChannelIE(InfoExtractor):
'description': 'md5:6dff1bb07cad8e5448e04daad9be1b30',
},
'playlist_mincount': 14,
},
{
}, {
'url': 'https://beta.floatplane.com/channel/bitwit_ultra/home',
'info_dict': {
'id': 'bitwit_ultra',
@ -525,8 +333,7 @@ class FloatplaneChannelIE(InfoExtractor):
'description': 'md5:1452f280bb45962976d4789200f676dd',
},
'playlist_mincount': 200,
},
]
}]
def _fetch_page(self, display_id, creator_id, channel_id, page):
query = {
@ -537,38 +344,26 @@ class FloatplaneChannelIE(InfoExtractor):
if channel_id:
query['channel'] = channel_id
page_data = self._download_json(
'https://www.floatplane.com/api/v3/content/creator',
display_id,
query=query,
note=f'Downloading page {page + 1}',
)
'https://www.floatplane.com/api/v3/content/creator', display_id,
query=query, note=f'Downloading page {page + 1}')
for post in page_data or []:
yield self.url_result(
f"https://www.floatplane.com/post/{post['id']}",
FloatplaneIE,
id=post['id'],
title=post.get('title'),
release_timestamp=parse_iso8601(post.get('releaseDate')),
)
f'https://www.floatplane.com/post/{post["id"]}',
FloatplaneIE, id=post['id'], title=post.get('title'),
release_timestamp=parse_iso8601(post.get('releaseDate')))
def _real_extract(self, url):
creator, channel = self._match_valid_url(url).group('id', 'channel')
display_id = join_nonempty(creator, channel, delim='/')
creator_data = self._download_json(
'https://www.floatplane.com/api/v3/creator/named', display_id, query={'creatorURL[0]': creator},
)[0]
'https://www.floatplane.com/api/v3/creator/named',
display_id, query={'creatorURL[0]': creator})[0]
channel_data = (
traverse_obj(creator_data, ('channels', lambda _, v: v['urlname'] == channel), get_all=False) or {}
)
channel_data = traverse_obj(
creator_data, ('channels', lambda _, v: v['urlname'] == channel), get_all=False) or {}
return self.playlist_result(
OnDemandPagedList(
functools.partial(self._fetch_page, display_id, creator_data['id'], channel_data.get('id')),
self._PAGE_SIZE,
),
display_id,
title=channel_data.get('title') or creator_data.get('title'),
description=channel_data.get('about') or creator_data.get('about'),
)
return self.playlist_result(OnDemandPagedList(functools.partial(
self._fetch_page, display_id, creator_data['id'], channel_data.get('id')), self._PAGE_SIZE),
display_id, title=channel_data.get('title') or creator_data.get('title'),
description=channel_data.get('about') or creator_data.get('about'))

Loading…
Cancel
Save