1
0
mirror of https://github.com/yt-dlp/yt-dlp.git synced 2025-06-27 17:08:32 +00:00
This commit is contained in:
doe1080 2025-06-25 23:38:30 +00:00 committed by GitHub
commit 1c8db913aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 174 additions and 0 deletions

View File

@ -638,6 +638,10 @@
FancodeLiveIE,
FancodeVodIE,
)
from .fanicon import (
FaniconFCIE,
FaniconIE,
)
from .fathom import FathomIE
from .faz import FazIE
from .fc2 import (

170
yt_dlp/extractor/fanicon.py Normal file
View File

@ -0,0 +1,170 @@
import itertools
from .common import InfoExtractor
from ..utils import (
ExtractorError,
determine_ext,
filter_dict,
int_or_none,
parse_iso8601,
str_or_none,
url_or_none,
)
from ..utils.traversal import traverse_obj
class FaniconBaseIE(InfoExtractor):
_BASE_URL = 'https://fanicon.net'
_API_BASE = f'{_BASE_URL}/api/fan'
class FaniconIE(FaniconBaseIE):
_VALID_URL = r'https?://fanicon\.net/(?:fanilive|web/fancommunities)/(?P<fc_id>\d+)(?:/livearchive)?/(?P<id>[\da-f]+)(?:/(?P<slug>[\da-f]+))?'
_TESTS = [{
'url': 'https://fanicon.net/fanilive/4937/1224907/83fd46f739df37d',
'info_dict': {
'id': '1224907',
'ext': 'mp4',
'title': 'YAYA RECORDS CREW',
'channel': 'YAYA RECORDS CREW',
'channel_id': '4937',
'display_id': '83fd46f739df37d',
'thumbnail': r're:https://.+\.jpg',
'timestamp': 1679835004,
'upload_date': '20230326',
'uploader': 'YAYA RECORDS',
'uploader_id': '819710',
},
}, {
'url': 'https://fanicon.net/web/fancommunities/5831/livearchive/1658622',
'info_dict': {
'id': '1658622',
'ext': 'mp4',
'title': 'ご相談配信❣️',
'channel': 'ワンコインのりこ通信',
'channel_id': '5831',
'comment_count': int,
'like_count': int,
'modified_date': '20250403',
'modified_timestamp': 1743686584,
'thumbnail': r're:https://.+\.jpg',
'timestamp': 1743678007,
'upload_date': '20250403',
'uploader': '芝崎典子',
'uploader_id': '1147555',
},
'skip': 'Only available for subscribers',
}]
def _parse_tron(self, key, webpage, video_id):
return self._search_json(
rf'window\.tron\.{key}\s*=\s*', webpage, f'{key} info', video_id, default={})
def _real_extract(self, url):
fc_id, video_id, slug = self._match_valid_url(url).groups()
webpage = self._download_webpage(url, video_id)
if slug:
live_info = self._parse_tron('live', webpage, video_id)
file_url = live_info['url']
is_live = not traverse_obj(live_info, ('isArchive', {bool}))
formats = self._extract_m3u8_formats(file_url, video_id, 'mp4', live=is_live)
metadata = {
'display_id': slug,
**traverse_obj(live_info, {
'title': ('body', {str}),
'timestamp': ('createdAt', {parse_iso8601}),
}),
}
else:
if not self._parse_tron('user', webpage, video_id):
self.raise_login_required()
cards = self._download_json(
f'{self._API_BASE}/cards/{video_id}', video_id, query={'fanclubId': fc_id})
if error := cards.get('error'):
raise ExtractorError(error['message'], expected=True)
file_url = cards['card']['media']['fileUrl']
is_live = traverse_obj(cards, ('card', 'media', 'isOnLive', {bool}))
if not (ext := determine_ext(file_url)):
raise ExtractorError('Unexpected file URL')
elif ext == 'm3u8':
formats = self._extract_m3u8_formats(file_url, video_id, 'mp4', live=is_live)
elif ext in {'mp3', 'mp4'}:
formats = [{'ext': ext, 'url': file_url}]
else:
self.raise_no_formats(f'Unexpected file type: {ext}')
metadata = traverse_obj(cards, ('card', {
'title': ('body', {str.strip}),
'comment_count': ('commentCount', {int}),
'like_count': ('likeCount', {int}),
'modified_timestamp': ('updatedAt', {parse_iso8601}),
'tags': ('tags', ..., {str}),
'timestamp': ('createdAt', {parse_iso8601}),
}))
return {
'id': video_id,
'formats': formats,
'is_live': is_live,
**metadata,
**traverse_obj(self._download_json(
f'{self._API_BASE}/fanclubs/{fc_id}', fc_id), ('fanclub', {
'channel': ('name', {str}),
'channel_id': ('id', {str_or_none}),
'thumbnail': ('heroImageUrl', {url_or_none}),
'uploader': ('owners', ..., 'name', {str}, any),
'uploader_id': (('ownerId', ('owners', ..., 'id')), {str_or_none}, any),
}),
),
}
class FaniconFCIE(FaniconBaseIE):
_VALID_URL = r'https?://fanicon\.net/web/fancommunities/(?P<id>\d+)(?:/scene)?$'
_TESTS = [{
'url': 'https://fanicon.net/web/fancommunities/5831/scene',
'info_dict': {
'id': '5831',
},
'playlist_mincount': 14,
'skip': 'Only available for subscribers',
}]
def _entries(self, fc_id):
min_id = None
for page in itertools.count(1):
cards = self._download_json(
f'{self._API_BASE}/cards', fc_id,
f'Downloading page {page}', query=filter_dict({
'fanclubId': fc_id,
'maxId': min_id,
}),
)
if not cards:
break
if error := cards.get('error'):
raise ExtractorError(error['message'], expected=True)
for media in traverse_obj(cards, ('cards', ..., 'media', {dict})):
if all([
traverse_obj(media, ('fileUrl', {url_or_none})),
traverse_obj(media, ('isOnLive', {bool})) is not True,
'images' not in media,
]):
yield self.url_result(
f'{self._BASE_URL}/web/fancommunities/{fc_id}/livearchive/{media["cardId"]}', FaniconIE)
if not (min_id := traverse_obj(cards, (
'cards', ..., 'id', {int_or_none}, filter, all, {min}, {str_or_none},
))):
break
def _real_extract(self, url):
fc_id = self._match_id(url)
return self.playlist_result(self._entries(fc_id), fc_id)