From 00a550ddf81b9acc0bd9b5c4c772e22aa79f4a81 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Sat, 15 Mar 2025 04:57:03 -0400 Subject: [PATCH 01/10] 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), + } From 3cab0b7db716b4a2d713f8dde6015ab132845e32 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Sat, 15 Mar 2025 05:18:47 -0400 Subject: [PATCH 02/10] fix format issues --- yt_dlp/extractor/myfreecams.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index 869afc460..1cd715a94 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -1,5 +1,5 @@ -from yt_dlp.utils._utils import ExtractorError from .common import InfoExtractor +from yt_dlp.utils._utils import ExtractorError class MyFreeCamsIE(InfoExtractor): @@ -12,12 +12,12 @@ class MyFreeCamsIE(InfoExtractor): 'ext': 'mp4', 'live_status': 'is_live', 'age_limit': 18, - 'thumbnail': 're:https://img.mfcimg.com/photos2/121/12172602/avatar.300x300.jpg\\?nc=\\d+' + 'thumbnail': 're:https://img.mfcimg.com/photos2/121/12172602/avatar.300x300.jpg\\?nc=\\d+', }, 'params': { 'skip_download': True, }, - 'skip': 'Model offline' + 'skip': 'Model offline', }, { 'url': 'https://share.myfreecams.com/BUSTY_EMA', 'info_dict': { @@ -26,12 +26,12 @@ class MyFreeCamsIE(InfoExtractor): 'ext': 'mp4', 'live_status': 'is_live', 'age_limit': 18, - 'thumbnail': 're:https://img.mfcimg.com/photos2/295/29538300/avatar.300x300.jpg\\?nc=\\d+' + 'thumbnail': 're:https://img.mfcimg.com/photos2/295/29538300/avatar.300x300.jpg\\?nc=\\d+', }, 'params': { 'skip_download': True, }, - 'skip': 'Model offline' + 'skip': 'Model offline', }, { 'url': 'https://www.myfreecams.com/#notbeckyhecky', 'info_dict': { @@ -40,33 +40,33 @@ class MyFreeCamsIE(InfoExtractor): 'ext': 'mp4', 'is_live': True, 'age_limit': 18, - 'thumbnail': 're:https://img.mfcimg.com/photos2/243/24308977/avatar.300x300.jpg\\?nc=\\d+' + 'thumbnail': 're:https://img.mfcimg.com/photos2/243/24308977/avatar.300x300.jpg\\?nc=\\d+', }, 'params': { 'skip_download': True, }, - 'skip': 'Model offline' + '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' + webpage, 'sid', ) mid = self._search_regex( [r'data-campreview-mid=["\'](\d+)["\']', r'data-cam-preview-model-id-value=["\'](\d+)["\']'], - webpage, 'mid' + webpage, 'mid', ) webrtc = self._search_regex( [r'data-is-webrtc=["\']([^"\']+)["\']', r'data-cam-preview-is-webrtc-value=["\']([^"\']+)["\']'], - webpage, 'webrtc', default='false' + webpage, 'webrtc', default='false', ) snap_url = self._search_regex( r'data-cam-preview-snap-url-value=["\']([^"\']+)["\']', - webpage, 'snap_url' + webpage, 'snap_url', ) webrtc = 'true' if 'mfc_a_' in snap_url else 'false' @@ -74,7 +74,7 @@ def get_required_params(self, webpage): return { 'sid': sid, 'mid': str(int(mid) + 100_000_000), - 'a': 'a_' if webrtc == 'true' else '' + 'a': 'a_' if webrtc == 'true' else '', } def _real_extract(self, url): From 735ce76e225416288419e6f3fbd6ba922e2c5d9b Mon Sep 17 00:00:00 2001 From: JChris246 Date: Sat, 15 Mar 2025 05:24:47 -0400 Subject: [PATCH 03/10] fix import --- yt_dlp/extractor/myfreecams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index 1cd715a94..1b802c290 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -1,5 +1,5 @@ from .common import InfoExtractor -from yt_dlp.utils._utils import ExtractorError +from ..utils import ExtractorError class MyFreeCamsIE(InfoExtractor): From 1dd3d4190d45b0de879d52ba1331718273ca2c44 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Sat, 15 Mar 2025 17:52:00 -0400 Subject: [PATCH 04/10] refactor: use UserNotLive error when offline --- yt_dlp/extractor/myfreecams.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index 1b802c290..c6817eeb3 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -1,5 +1,5 @@ from .common import InfoExtractor -from ..utils import ExtractorError +from ..utils import ExtractorError, UserNotLive class MyFreeCamsIE(InfoExtractor): @@ -87,7 +87,7 @@ def _real_extract(self, url): params = self.get_required_params(webpage) if not params['sid']: - raise ExtractorError('Model offline') + raise UserNotLive('Model offline') return { 'id': video_id, From e13730aefa05b7d6957c352de7ddf2f7456ee4f3 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Mon, 17 Mar 2025 22:47:48 -0400 Subject: [PATCH 05/10] refactor: use api for extraction --- yt_dlp/extractor/myfreecams.py | 89 +++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index c6817eeb3..f4a1db61f 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -1,5 +1,11 @@ +import random + from .common import InfoExtractor -from ..utils import ExtractorError, UserNotLive +from ..utils import ( + ExtractorError, + UserNotLive, + traverse_obj, +) class MyFreeCamsIE(InfoExtractor): @@ -51,12 +57,12 @@ class MyFreeCamsIE(InfoExtractor): 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', + webpage, 'sid', fatal=False ) mid = self._search_regex( [r'data-campreview-mid=["\'](\d+)["\']', r'data-cam-preview-model-id-value=["\'](\d+)["\']'], - webpage, 'mid', + webpage, 'mid', fatal=False ) webrtc = self._search_regex( @@ -66,19 +72,21 @@ def get_required_params(self, webpage): snap_url = self._search_regex( r'data-cam-preview-snap-url-value=["\']([^"\']+)["\']', - webpage, 'snap_url', + 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 _real_extract(self, url): - video_id = self._match_id(url) + 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+)[^\'"]*', @@ -86,7 +94,7 @@ def _real_extract(self, url): raise ExtractorError('Model not found') params = self.get_required_params(webpage) - if not params['sid']: + if not params.get('sid'): raise UserNotLive('Model offline') return { @@ -99,3 +107,70 @@ def _real_extract(self, url): '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, 'mp4', 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'), + } From 57c5540f5a84f74993a848acc1db062e0116bad9 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Mon, 17 Mar 2025 22:49:40 -0400 Subject: [PATCH 06/10] fix: add trailing commas --- yt_dlp/extractor/myfreecams.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index f4a1db61f..3cf555a72 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -57,12 +57,12 @@ class MyFreeCamsIE(InfoExtractor): 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 + 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 + webpage, 'mid', fatal=False, ) webrtc = self._search_regex( @@ -72,7 +72,7 @@ def get_required_params(self, webpage): snap_url = self._search_regex( r'data-cam-preview-snap-url-value=["\']([^"\']+)["\']', - webpage, 'snap_url', default='' + webpage, 'snap_url', default='', ) webrtc = 'true' if 'mfc_a_' in snap_url else 'false' From c3f86881df369ae07f7964f2897a54551f22e0d9 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Mon, 17 Mar 2025 23:09:45 -0400 Subject: [PATCH 07/10] refactor: return both hls and llhls streams --- yt_dlp/extractor/myfreecams.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index 3cf555a72..2fb6dd688 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -96,14 +96,18 @@ def webpage_extraction(self, video_id): 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_mobile/playlist.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': 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), + '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), } @@ -160,7 +164,10 @@ def _real_extract(self, url): 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, 'mp4', live=True) + 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_cmaf/playlist_sfm4s.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') From b450008bc0da34da176bd3daf3f21847c465d5a1 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Mon, 17 Mar 2025 23:11:13 -0400 Subject: [PATCH 08/10] fix lint errors --- yt_dlp/extractor/myfreecams.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index 2fb6dd688..05ba61261 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -96,12 +96,12 @@ def webpage_extraction(self, video_id): 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_mobile/playlist.m3u8', - video_id, ext='mp4', m3u8_id='llhls', live=True) + 'https://edgevideo.myfreecams.com/llhls/NxServer/' + params['sid'] + '/ngrp:mfc_' + params['a'] + params['mid'] + '.f4v_mobile/playlist.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)) + video_id, ext='mp4', m3u8_id='hls', live=True)) return { 'id': video_id, From 5b1b1598286e0cf437dd7d312c7e65edd2001e88 Mon Sep 17 00:00:00 2001 From: JChris246 Date: Mon, 17 Mar 2025 23:37:22 -0400 Subject: [PATCH 09/10] fix: update m3u8 urls --- yt_dlp/extractor/myfreecams.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index 05ba61261..808bc4574 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -98,7 +98,7 @@ def webpage_extraction(self, video_id): raise UserNotLive('Model offline') formats = self._extract_m3u8_formats( - 'https://edgevideo.myfreecams.com/llhls/NxServer/' + params['sid'] + '/ngrp:mfc_' + params['a'] + params['mid'] + '.f4v_mobile/playlist.m3u8', + '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)) @@ -166,7 +166,7 @@ def _real_extract(self, url): 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_cmaf/playlist_sfm4s.m3u8?nc={rand_val}&v=1.97.23', + 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: From f1213cb3281264da432336de097d6200da85645b Mon Sep 17 00:00:00 2001 From: JChris246 Date: Tue, 18 Mar 2025 21:37:02 -0400 Subject: [PATCH 10/10] feat: add additional url type --- yt_dlp/extractor/myfreecams.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/yt_dlp/extractor/myfreecams.py b/yt_dlp/extractor/myfreecams.py index 808bc4574..8f7b03a99 100644 --- a/yt_dlp/extractor/myfreecams.py +++ b/yt_dlp/extractor/myfreecams.py @@ -9,7 +9,7 @@ class MyFreeCamsIE(InfoExtractor): - _VALID_URL = r'https?://(?:app|share|www)\.myfreecams\.com(?:/room)?/#?(?P[^/?&#]+)' + _VALID_URL = r'https?://(?:app|share|www|m)\.myfreecams\.com(?:/room|/chats)?/#?(?P[^/?&#]+)' _TESTS = [{ 'url': 'https://app.myfreecams.com/room/stacy_x3', 'info_dict': { @@ -52,6 +52,9 @@ class MyFreeCamsIE(InfoExtractor): 'skip_download': True, }, 'skip': 'Model offline', + }, { + 'url': 'https://m.myfreecams.com/chats/erikasmagic', + 'only_matching': True, }] def get_required_params(self, webpage):