1
0
mirror of https://github.com/yt-dlp/yt-dlp.git synced 2025-08-16 09:28:28 +00:00
This commit is contained in:
JChris246 2025-08-13 08:28:45 +03:00 committed by GitHub
commit 9fdf961c9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 187 additions and 0 deletions

View File

@ -1218,6 +1218,7 @@
MxplayerIE,
MxplayerShowIE,
)
from .myfreecams import MyFreeCamsIE
from .myspace import (
MySpaceAlbumIE,
MySpaceIE,

View File

@ -0,0 +1,186 @@
import random
from .common import InfoExtractor
from ..utils import (
ExtractorError,
UserNotLive,
traverse_obj,
)
class MyFreeCamsIE(InfoExtractor):
_VALID_URL = r'https?://(?:app|share|www|m)\.myfreecams\.com(?:/room|/chats)?/#?(?P<id>[^/?&#]+)'
_TESTS = [{
'url': 'https://app.myfreecams.com/room/stacy_x3',
'info_dict': {
'id': 'stacy_x3',
'title': 're:^stacy_x3 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
'ext': 'mp4',
'live_status': 'is_live',
'age_limit': 18,
'thumbnail': 're:https://img.mfcimg.com/photos2/121/12172602/avatar.300x300.jpg\\?nc=\\d+',
},
'params': {
'skip_download': True,
},
'skip': 'Model offline',
}, {
'url': 'https://share.myfreecams.com/BUSTY_EMA',
'info_dict': {
'id': 'BUSTY_EMA',
'title': 're:^BUSTY_EMA [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
'ext': 'mp4',
'live_status': 'is_live',
'age_limit': 18,
'thumbnail': 're:https://img.mfcimg.com/photos2/295/29538300/avatar.300x300.jpg\\?nc=\\d+',
},
'params': {
'skip_download': True,
},
'skip': 'Model offline',
}, {
'url': 'https://www.myfreecams.com/#notbeckyhecky',
'info_dict': {
'id': 'notbeckyhecky',
'title': 're:^notbeckyhecky [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
'ext': 'mp4',
'is_live': True,
'age_limit': 18,
'thumbnail': 're:https://img.mfcimg.com/photos2/243/24308977/avatar.300x300.jpg\\?nc=\\d+',
},
'params': {
'skip_download': True,
},
'skip': 'Model offline',
}, {
'url': 'https://m.myfreecams.com/chats/erikasmagic',
'only_matching': True,
}]
def get_required_params(self, webpage):
sid = self._search_regex(
[r'data-campreview-sid=["\'](\d+)["\']', r'data-cam-preview-server-id-value=["\'](\d+)["\']'],
webpage, 'sid', fatal=False,
)
mid = self._search_regex(
[r'data-campreview-mid=["\'](\d+)["\']', r'data-cam-preview-model-id-value=["\'](\d+)["\']'],
webpage, 'mid', fatal=False,
)
webrtc = self._search_regex(
[r'data-is-webrtc=["\']([^"\']+)["\']', r'data-cam-preview-is-webrtc-value=["\']([^"\']+)["\']'],
webpage, 'webrtc', default='false',
)
snap_url = self._search_regex(
r'data-cam-preview-snap-url-value=["\']([^"\']+)["\']',
webpage, 'snap_url', default='',
)
webrtc = 'true' if 'mfc_a_' in snap_url else 'false'
if not sid or not mid:
return {}
return {
'sid': sid,
'mid': str(int(mid) + 100_000_000),
'a': 'a_' if webrtc == 'true' else '',
}
def webpage_extraction(self, video_id):
webpage = self._download_webpage('https://share.myfreecams.com/' + video_id, video_id)
if not self._search_regex(r'https://www.myfreecams.com/php/tracking.php\?[^\'"]*model_id=(\d+)[^\'"]*',
webpage, 'model id'):
raise ExtractorError('Model not found')
params = self.get_required_params(webpage)
if not params.get('sid'):
raise UserNotLive('Model offline')
formats = self._extract_m3u8_formats(
'https://edgevideo.myfreecams.com/llhls/NxServer/' + params['sid'] + '/ngrp:mfc_' + params['a'] + params['mid'] + '.f4v_cmaf/playlist_sfm4s.m3u8',
video_id, ext='mp4', m3u8_id='llhls', live=True)
formats.extend(self._extract_m3u8_formats('https://edgevideo.myfreecams.com/hls/NxServer/' + params['sid'] + '/ngrp:mfc_' + params['a'] + params['mid'] + '.f4v_mobile/playlist.m3u8',
video_id, ext='mp4', m3u8_id='hls', live=True))
return {
'id': video_id,
'title': video_id,
'is_live': True,
'formats': formats,
'age_limit': 18,
'thumbnail': self._search_regex(r'(https?://img\.mfcimg\.com/photos2?/\d+/\d+/avatar\.\d+x\d+.jpg(?:\?nc=\d+)?)', webpage, 'thumbnail', fatal=False),
}
def _real_extract(self, url):
video_id = self._match_id(url)
user_data = self._download_json(
'https://api-edge.myfreecams.com/usernameLookup/' + video_id, video_id)
if not user_data:
self.report_warning('Unable to get user data from api, falling back to webpage extraction')
return self.webpage_extraction(video_id)
user = traverse_obj(user_data, ('result', 'user'))
if not user:
raise ExtractorError('Model ' + video_id + ' not found')
if not user.get('id'):
raise ExtractorError('Model ' + video_id + ' id not found')
if user.get('access_level') != 4:
raise ExtractorError('User ' + video_id + ' is not a model')
status = user.get('vs')
if status is None:
raise UserNotLive('Model ' + video_id + ' offline', expected=True)
user_sessions = user.get('sessions')
if not user_sessions or len(user_sessions) < 1:
self.report_warning('Unable to get user sessions from api, falling back to webpage extraction')
return self.webpage_extraction(video_id)
session = next((item for item in user_sessions if item.get('server_name')), None)
if session is None:
self.report_warning('Unable to get valid user session from api, falling back to webpage extraction')
return self.webpage_extraction(video_id)
vs = session.get('vstate')
ok_vs = [0, 90]
if vs not in ok_vs:
if vs == 127:
raise UserNotLive('Model ' + video_id + ' is offline', expected=True)
elif vs == 12:
raise ExtractorError('Model ' + video_id + ' is in a private show', expected=True)
elif vs == 13:
raise ExtractorError('Model ' + video_id + ' is in a group show', expected=True)
elif vs == 2:
raise ExtractorError('Model ' + video_id + ' is away', expected=True)
else:
raise ExtractorError('Unknown status ' + str(vs) + ' for model ' + video_id)
server_id = session.get('server_name')[5:]
phase = session.get('phase')
mid = int(user.get('id')) + 100_000_000
rand_val = random.random()
formats = self._extract_m3u8_formats(
f'https://edgevideo.myfreecams.com/llhls/NxServer/{server_id}/ngrp:mfc_{phase}{mid}.f4v_cmaf/playlist_sfm4s.m3u8?nc={rand_val}&v=1.97.23',
video_id, ext='mp4', m3u8_id='llhls', live=True)
formats.extend(self._extract_m3u8_formats(
f'https://edgevideo.myfreecams.com/hls/NxServer/{server_id}/ngrp:mfc_{phase}{mid}.f4v_mobile/playlist.m3u8?nc={rand_val}&v=1.97.23',
video_id, ext='mp4', m3u8_id='hls', live=True))
if not formats or len(formats) < 1:
self.report_warning('Unable to stream urls from api, falling back to webpage extraction')
return self.webpage_extraction(video_id)
return {
'id': video_id,
'title': video_id,
'is_live': True,
'formats': formats,
'age_limit': 18,
'thumbnail': user.get('avatar'),
}