From 00a550ddf81b9acc0bd9b5c4c772e22aa79f4a81 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Sat, 15 Mar 2025 04:57:03 -0400 Subject: [PATCH] feat: add new extractor myfreecams --- yt_dlp/extractor/_extractors.py | 1 + yt_dlp/extractor/myfreecams.py | 101 ++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 yt_dlp/extractor/myfreecams.py diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 3ab0f5efa..1896592b1 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -1219,6 +1219,7 @@ MxplayerIE, MxplayerShowIE, ) +from .myfreecams import MyFreeCamsIE from .myspace import ( MySpaceAlbumIE, MySpaceIE, diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py new file mode 100644 index 000000000..869afc460 --- /dev/null +++ b/yt_dlp/extractor/myfreecams.py @@ -0,0 +1,101 @@ +from yt_dlp.utils._utils import ExtractorError +from .common import InfoExtractor + + +class MyFreeCamsIE(InfoExtractor): + _VALID_URL = r'https?://(?:app|share|www)\.myfreecams\.com(?:/room)?/#?(?P[^/?&#]+)' + _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' + }] + + 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' + ) + + mid = self._search_regex( + [r'data-campreview-mid=["\'](\d+)["\']', r'data-cam-preview-model-id-value=["\'](\d+)["\']'], + webpage, 'mid' + ) + + 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' + ) + + webrtc = 'true' if 'mfc_a_' in snap_url else 'false' + + return { + 'sid': sid, + 'mid': str(int(mid) + 100_000_000), + 'a': 'a_' if webrtc == 'true' else '' + } + + def _real_extract(self, url): + video_id = self._match_id(url) + 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['sid']: + raise ExtractorError('Model offline') + + return { + 'id': video_id, + 'title': video_id, + 'is_live': True, + 'formats': self._extract_m3u8_formats( + 'https://edgevideo.myfreecams.com/hls/NxServer/' + params['sid'] + '/ngrp:mfc_' + params['a'] + params['mid'] + '.f4v_mobile/playlist.m3u8', + video_id, 'mp4', live=True), + '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), + }