From e3c4d2314973dab3a0034e61f32fad834763ca2d Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:21:39 +0000 Subject: [PATCH 01/10] Fix shahid.py & add ShahidLiveIE --- yt_dlp/extractor/shahid.py | 338 ++++++++++++++++++++++++------------- 1 file changed, 219 insertions(+), 119 deletions(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index f0a3b6b7d7..a39658cfd7 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -1,165 +1,265 @@ import json import math -import re +import urllib.parse -from .aws import AWSIE -from ..networking.exceptions import HTTPError +from .common import InfoExtractor from ..utils import ( ExtractorError, InAdvancePagedList, - clean_html, int_or_none, + orderedSet, parse_iso8601, str_or_none, - urlencode_postdata, ) -class ShahidBaseIE(AWSIE): - _AWS_PROXY_HOST = 'api2.shahid.net' - _AWS_API_KEY = '2RRtuMHx95aNI1Kvtn2rChEuwsCogUd4samGPjLh' +class ShahidBaseIE(InfoExtractor): + _API_BASE = 'https://api3.shahid.net/proxy/v2.1' _VALID_URL_BASE = r'https?://shahid\.mbc\.net/[a-z]{2}/' - def _handle_error(self, e): - fail_data = self._parse_json( - e.cause.response.read().decode('utf-8'), None, fatal=False) - if fail_data: - faults = fail_data.get('faults', []) - faults_message = ', '.join([clean_html(fault['userMessage']) for fault in faults if fault.get('userMessage')]) - if faults_message: - raise ExtractorError(faults_message, expected=True) - - def _call_api(self, path, video_id, request=None): - query = {} - if request: - query['request'] = json.dumps(request) - try: - return self._aws_execute_api({ - 'uri': '/proxy/v2/' + path, - 'access_key': 'AKIAI6X4TYCIXM2B7MUQ', - 'secret_key': '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', - }, video_id, query) - except ExtractorError as e: - if isinstance(e.cause, HTTPError): - self._handle_error(e) - raise + def _call_api(self, path, item_id, request): + api_url = f'{self._API_BASE}/{path}' + query_params = {'request': json.dumps(request)} + query_string = urllib.parse.urlencode(query_params) + return self._download_json(f'{api_url}?{query_string}', item_id) + + def _call_playout_api(self, video_id, country): + query_params = {'outputParameter': 'vmap'} + if country: + query_params['country'] = country + query_string = urllib.parse.urlencode(query_params) + api_url = f'{self._API_BASE}/playout/new/url/{video_id}?{query_string}' + + return self._download_json(api_url, video_id, note=f'Downloading API JSON for country={country}', fatal=False) + + def get_formats_subtitles(self, video_id, live): + geo_bypass_country = self.get_param('geo_bypass_country', None) + # Try multiple country codes for geo-unblocking + countries = orderedSet((geo_bypass_country, None, 'SA', 'AE', 'EG', 'US')) + response = None + for country_code in countries: + try: + response = self._call_playout_api(video_id, country_code) + if response: + break + except Exception as e: + self.write_debug(f'API call failed for country={country_code}: {e}') + continue + + if not response: + raise ExtractorError('Unable to get a successful API response for ' + video_id) + + playout = response.get('playout', {}) + if not self.get_param('allow_unplayable_formats') and playout.get('drm', False): + self.report_drm(video_id) + + stream_url = playout.get('url') + if not stream_url: + raise ExtractorError('Stream URL not found in API response.') + + # Remove query from stream_url that restricts video resolutions + cleaned_url = self.remove_params(stream_url) + + formats = [] + subtitles = [] + for url in orderedSet((cleaned_url, stream_url)): + try: + formats, subtitles = self._extract_m3u8_formats_and_subtitles( + stream_url, video_id, 'mp4', live=live) + if formats: + break + except Exception as e: + self.report_warning(f'Failed to extract formats from {url}: {e}') + continue + + return formats, subtitles + + def _get_product_info(self, video_id): + return self._call_api('product/id', video_id, { + 'id': video_id, + }) + + def remove_params(self, url): + if url: + parsed_url = urllib.parse.urlparse(url) + return urllib.parse.urlunparse(parsed_url._replace(query='')) + return url class ShahidIE(ShahidBaseIE): - _NETRC_MACHINE = 'shahid' - _VALID_URL = ShahidBaseIE._VALID_URL_BASE + r'(?:serie|show|movie)s/[^/]+/(?Pepisode|clip|movie)-(?P\d+)' + _VALID_URL = ShahidBaseIE._VALID_URL_BASE + r'player/(?Pclips|episodes)/(?P[^/]+)/id-(?P<id>\d+)' _TESTS = [{ - 'url': 'https://shahid.mbc.net/ar/shows/%D9%85%D8%AA%D8%AD%D9%81-%D8%A7%D9%84%D8%AF%D8%AD%D9%8A%D8%AD-%D8%A7%D9%84%D9%85%D9%88%D8%B3%D9%85-1-%D9%83%D9%84%D9%8A%D8%A8-1/clip-816924', + 'url': 'https://shahid.mbc.net/ar/player/clips/Al-Daheeh-Museum-season-1-clip-1/id-816924', 'info_dict': { 'id': '816924', 'ext': 'mp4', - 'title': 'متحف الدحيح الموسم 1 كليب 1', + 'title': 'برومو', 'timestamp': 1602806400, 'upload_date': '20201016', 'description': 'برومو', 'duration': 22, 'categories': ['كوميديا'], + 'thumbnail': r're:^https?://.*\.jpg$', + 'series': 'متحف الدحيح', + 'season': 1, + 'season_number': 1, + 'season_id': '816485', + 'episode': 'Episode 1', + 'episode_number': 1, + 'episode_id': '816924', }, 'params': { # m3u8 download 'skip_download': True, }, }, { - 'url': 'https://shahid.mbc.net/ar/movies/%D8%A7%D9%84%D9%82%D9%86%D8%A7%D8%B5%D8%A9/movie-151746', - 'only_matching': True, + 'url': 'https://shahid.mbc.net/en/player/episodes/Ramez-Fi-Al-Shallal-season-1-episode-1/id-359319', + 'info_dict': { + 'id': '359319', + 'title': 'فيفي عبدو', + 'description': 'فيفي عبدو', + 'duration': 1530, + 'thumbnail': r're:^https?://.*\.jpg$', + 'categories': ['كوميديا', 'مصري', 'تلفزيون الواقع'], + 'series': 'رامز في الشلال', + 'season': 'Season 1', + 'season_number': 1, + 'season_id': '357909', + 'episode': 'Episode 1', + 'episode_number': 1, + 'episode_id': '359319', + 'timestamp': 1557162000, + 'upload_date': '20190506', + 'ext': 'mp4', + }, }, { - # shahid plus subscriber only - 'url': 'https://shahid.mbc.net/ar/series/%D9%85%D8%B1%D8%A7%D9%8A%D8%A7-2011-%D8%A7%D9%84%D9%85%D9%88%D8%B3%D9%85-1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/episode-90511', - 'only_matching': True, + 'url': 'https://shahid.mbc.net/en/player/episodes/Maraya-season-1-episode-1/id-985363', + 'info_dict': { + 'id': '985363', + 'title': 'مرايا', + 'description': '', + 'thumbnail': r're:^https?://.*\.jpg$', + 'duration': 3144, + 'timestamp': 1683158400, + 'categories': ['كوميديا', 'سوري'], + 'series': 'مرايا', + 'episode_number': 1, + 'episode_id': '985363', + 'upload_date': '20230504', + 'episode': 'Episode 1', + 'season': 1, + 'season_number': 1, + 'season_id': '985240', + 'ext': 'mp4', + }, }, { - 'url': 'https://shahid.mbc.net/en/shows/Ramez-Fi-Al-Shallal-season-1-episode-1/episode-359319', - 'only_matching': True, + 'url': 'https://shahid.mbc.net/ar/player/episodes/Bab-Al-Hara-season-3-episode-17/id-76878', + 'info_dict': { + 'id': '76878', + 'title': 'باب الحارة', + 'description': '', + 'thumbnail': r're:^https?://.*\.jpg$', + 'duration': 2647, + 'timestamp': 1399334400, + 'categories': ['إجتماعي', 'سوري', 'دراما'], + 'series': 'باب الحارة', + 'episode_number': 17, + 'episode_id': '76878', + 'upload_date': '20140506', + 'episode': 'Episode 17', + 'season': 3, + 'season_number': 3, + 'season_id': '68680', + 'ext': 'mp4', + }, }] - def _perform_login(self, username, password): - try: - user_data = self._download_json( - 'https://shahid.mbc.net/wd/service/users/login', - None, 'Logging in', data=json.dumps({ - 'email': username, - 'password': password, - 'basic': 'false', - }).encode(), headers={ - 'Content-Type': 'application/json; charset=UTF-8', - })['user'] - except ExtractorError as e: - if isinstance(e.cause, HTTPError): - self._handle_error(e) - raise - - self._download_webpage( - 'https://shahid.mbc.net/populateContext', - None, 'Populate Context', data=urlencode_postdata({ - 'firstName': user_data['firstName'], - 'lastName': user_data['lastName'], - 'userName': user_data['email'], - 'csg_user_name': user_data['email'], - 'subscriberId': user_data['id'], - 'sessionId': user_data['sessionId'], - })) - def _real_extract(self, url): - page_type, video_id = self._match_valid_url(url).groups() - if page_type == 'clip': - page_type = 'episode' + video_id = self._match_id(url) + formats, subtitles = self.get_formats_subtitles(video_id, live=False) + + product_info_response = self._get_product_info(video_id) + product = product_info_response.get('productModel', {}) + show = product.get('show', {}) + season = show.get('season', {}) - playout = self._call_api( - 'playout/new/url/' + video_id, video_id)['playout'] + return { + 'id': video_id, + 'title': str_or_none(product.get('title') or show.get('title')), + 'description': str_or_none(product.get('description')), + 'thumbnail': str_or_none(self.remove_params(product.get('thumbnailImage'))), + 'duration': int_or_none(product.get('duration')), + 'timestamp': parse_iso8601(product.get('createdDate')), + 'categories': [genre.get('title') for genre in product.get('genres', []) if genre.get('title')], + 'series': str_or_none(show.get('title')), + 'season': int_or_none(season.get('seasonName')), + 'season_number': int_or_none(season.get('seasonNumber')), + 'season_id': str_or_none(season.get('id')), + 'episode_number': int_or_none(product.get('number')), + 'episode_id': video_id, + 'formats': formats, + 'subtitles': subtitles, + 'ext': 'mp4', + } - if not self.get_param('allow_unplayable_formats') and playout.get('drm'): - self.report_drm(video_id) - formats = self._extract_m3u8_formats(re.sub( - # https://docs.aws.amazon.com/mediapackage/latest/ug/manifest-filtering.html - r'aws\.manifestfilter=[\w:;,-]+&?', - '', playout['url']), video_id, 'mp4') - - # video = self._call_api( - # 'product/id', video_id, { - # 'id': video_id, - # 'productType': 'ASSET', - # 'productSubType': page_type.upper() - # })['productModel'] - - response = self._download_json( - f'http://api.shahid.net/api/v1_1/{page_type}/{video_id}', - video_id, 'Downloading video JSON', query={ - 'apiKey': 'sh@hid0nlin3', - 'hash': 'b2wMCTHpSmyxGqQjJFOycRmLSex+BpTK/ooxy6vHaqs=', - }) - data = response.get('data', {}) - error = data.get('error') - if error: - raise ExtractorError( - '{} returned error: {}'.format(self.IE_NAME, '\n'.join(error.values())), - expected=True) - - video = data[page_type] - title = video['title'] - categories = [ - category['name'] - for category in video.get('genres', []) if 'name' in category] +class ShahidLiveIE(ShahidBaseIE): + _VALID_URL = ShahidBaseIE._VALID_URL_BASE + r'?livestream/[^/]+/livechannel-(?P<id>\d+)' + _TESTS = [{ + 'url': 'https://shahid.mbc.net/en/livestream/SBC/livechannel-946940', + 'only_matching': True, # DRM Protected + }, { + 'url': 'https://shahid.mbc.net/fr/livestream/Wanasa/livechannel-414449', + 'info_dict': { + 'id': '414449', + 'title': str, + 'live_status': 'is_live', + 'description': 'md5:eba66fad0a5fd9c8081d5158145dc924', + 'thumbnail': str, + 'categories': ['عمل غنائي', 'موسيقي'], + 'ext': 'mp4', + }, + }, { + 'url': 'https://shahid.mbc.net/en/livestream/MBC1/livechannel-387238', + 'info_dict': { + 'id': '387238', + 'title': str, + 'live_status': 'is_live', + 'description': 'md5:2562c67c7897e59c763c713c6a7712ec', + 'thumbnail': str, + 'categories': ['دراما'], + 'ext': 'mp4', + }, + }, { + 'url': 'https://shahid.mbc.net/ar/livestream/MBC1/livechannel-816764', # Requires country = 'US' + 'info_dict': { + 'id': '816764', + 'title': str, + 'live_status': 'is_live', + 'description': 'md5:14faec01d54423a5dadaef27dd525130', + 'thumbnail': str, + 'categories': ['دراما'], + 'ext': 'mp4', + }, + }] + + def _real_extract(self, url): + video_id = self._match_id(url) + + product_info_response = self._get_product_info(video_id) + product = product_info_response.get('productModel', {}) + formats, subtitles = self.get_formats_subtitles(video_id, live=True) return { 'id': video_id, - 'title': title, - 'description': video.get('description'), - 'thumbnail': video.get('thumbnailUrl'), - 'duration': int_or_none(video.get('duration')), - 'timestamp': parse_iso8601(video.get('referenceDate')), - 'categories': categories, - 'series': video.get('showTitle') or video.get('showName'), - 'season': video.get('seasonTitle'), - 'season_number': int_or_none(video.get('seasonNumber')), - 'season_id': str_or_none(video.get('seasonId')), - 'episode_number': int_or_none(video.get('number')), - 'episode_id': video_id, + 'title': str_or_none(product.get('title')), + 'description': str_or_none(product.get('description')), + 'thumbnail': str_or_none(self.remove_params(product.get('thumbnailImage'))), + 'categories': [genre.get('title') for genre in product.get('genres', []) if genre.get('title')], 'formats': formats, + 'subtitles': subtitles, + 'ext': 'mp4', + 'is_live': True, } @@ -170,7 +270,7 @@ class ShahidShowIE(ShahidBaseIE): 'info_dict': { 'id': '79187', 'title': 'رامز قرش البحر', - 'description': 'md5:c88fa7e0f02b0abd39d417aee0d046ff', + 'description': 'md5:d85c0675eb07251f9ec5273ee1979496', }, 'playlist_mincount': 32, }, { From 4c1bf4b01ca61741c1fc64ac5f70ca56707314d8 Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:22:01 +0000 Subject: [PATCH 02/10] Add ShahidLiveIE --- yt_dlp/extractor/_extractors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 25bad4f0dc..5c68a31295 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -1839,6 +1839,7 @@ from .seznamzpravy import ( ) from .shahid import ( ShahidIE, + ShahidLiveIE, ShahidShowIE, ) from .sharepoint import SharePointIE From 7213d740ac3b75c65d56fc93cd6ad04ab31df922 Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:28:38 +0000 Subject: [PATCH 03/10] Add back existing manifestfilter regex --- yt_dlp/extractor/shahid.py | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index a39658cfd7..5467af36b2 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -1,5 +1,6 @@ import json import math +import re import urllib.parse from .common import InfoExtractor @@ -57,22 +58,10 @@ class ShahidBaseIE(InfoExtractor): if not stream_url: raise ExtractorError('Stream URL not found in API response.') - # Remove query from stream_url that restricts video resolutions - cleaned_url = self.remove_params(stream_url) - - formats = [] - subtitles = [] - for url in orderedSet((cleaned_url, stream_url)): - try: - formats, subtitles = self._extract_m3u8_formats_and_subtitles( - stream_url, video_id, 'mp4', live=live) - if formats: - break - except Exception as e: - self.report_warning(f'Failed to extract formats from {url}: {e}') - continue - - return formats, subtitles + return self._extract_m3u8_formats_and_subtitles(re.sub( + # https://docs.aws.amazon.com/mediapackage/latest/ug/manifest-filtering.html + r'aws\.manifestfilter=[\w:;,-]+&?', + '', stream_url), video_id, 'mp4', live=live) def _get_product_info(self, video_id): return self._call_api('product/id', video_id, { From b16d0315c69c357509ac10708c57caec67157c8e Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:36:42 +0000 Subject: [PATCH 04/10] Update shahid.py --- yt_dlp/extractor/shahid.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index 5467af36b2..c67b72d299 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -63,9 +63,9 @@ class ShahidBaseIE(InfoExtractor): r'aws\.manifestfilter=[\w:;,-]+&?', '', stream_url), video_id, 'mp4', live=live) - def _get_product_info(self, video_id): - return self._call_api('product/id', video_id, { - 'id': video_id, + def _get_product_info(self, product_id): + return self._call_api('product/id', product_id, { + 'id': product_id, }) def remove_params(self, url): From 136e04470d3757a40a9c735cb9713b553eea6129 Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:51:50 +0000 Subject: [PATCH 05/10] Generate title if missing --- yt_dlp/extractor/shahid.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index c67b72d299..68eae66210 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -125,7 +125,7 @@ class ShahidIE(ShahidBaseIE): 'url': 'https://shahid.mbc.net/en/player/episodes/Maraya-season-1-episode-1/id-985363', 'info_dict': { 'id': '985363', - 'title': 'مرايا', + 'title': 'مرايا S1E1', 'description': '', 'thumbnail': r're:^https?://.*\.jpg$', 'duration': 3144, @@ -145,7 +145,7 @@ class ShahidIE(ShahidBaseIE): 'url': 'https://shahid.mbc.net/ar/player/episodes/Bab-Al-Hara-season-3-episode-17/id-76878', 'info_dict': { 'id': '76878', - 'title': 'باب الحارة', + 'title': 'باب الحارة S3E17', 'description': '', 'thumbnail': r're:^https?://.*\.jpg$', 'duration': 2647, @@ -172,19 +172,25 @@ class ShahidIE(ShahidBaseIE): show = product.get('show', {}) season = show.get('season', {}) + title = product.get('title') + series = show.get('title') + season_number = season.get('seasonNumber') + episode_number = product.get('number') + if not title and series: + title = series + ' S' + str(season_number) + 'E' + str(episode_number) return { 'id': video_id, - 'title': str_or_none(product.get('title') or show.get('title')), + 'title': str_or_none(title), 'description': str_or_none(product.get('description')), 'thumbnail': str_or_none(self.remove_params(product.get('thumbnailImage'))), 'duration': int_or_none(product.get('duration')), 'timestamp': parse_iso8601(product.get('createdDate')), 'categories': [genre.get('title') for genre in product.get('genres', []) if genre.get('title')], - 'series': str_or_none(show.get('title')), + 'series': str_or_none(series), 'season': int_or_none(season.get('seasonName')), - 'season_number': int_or_none(season.get('seasonNumber')), + 'season_number': int_or_none(season_number), 'season_id': str_or_none(season.get('id')), - 'episode_number': int_or_none(product.get('number')), + 'episode_number': int_or_none(episode_number), 'episode_id': video_id, 'formats': formats, 'subtitles': subtitles, From b4d17d78f622ca90196b6953f9e224b924a6da5e Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:58:16 +0000 Subject: [PATCH 06/10] Don't generate title if missing --- yt_dlp/extractor/shahid.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index 68eae66210..30bae86fa3 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -125,7 +125,7 @@ class ShahidIE(ShahidBaseIE): 'url': 'https://shahid.mbc.net/en/player/episodes/Maraya-season-1-episode-1/id-985363', 'info_dict': { 'id': '985363', - 'title': 'مرايا S1E1', + 'title': 'Shahid video #985363', 'description': '', 'thumbnail': r're:^https?://.*\.jpg$', 'duration': 3144, @@ -145,7 +145,7 @@ class ShahidIE(ShahidBaseIE): 'url': 'https://shahid.mbc.net/ar/player/episodes/Bab-Al-Hara-season-3-episode-17/id-76878', 'info_dict': { 'id': '76878', - 'title': 'باب الحارة S3E17', + 'title': 'Shahid video #76878', 'description': '', 'thumbnail': r're:^https?://.*\.jpg$', 'duration': 2647, @@ -172,25 +172,19 @@ class ShahidIE(ShahidBaseIE): show = product.get('show', {}) season = show.get('season', {}) - title = product.get('title') - series = show.get('title') - season_number = season.get('seasonNumber') - episode_number = product.get('number') - if not title and series: - title = series + ' S' + str(season_number) + 'E' + str(episode_number) return { 'id': video_id, - 'title': str_or_none(title), + 'title': str_or_none(product.get('title')), 'description': str_or_none(product.get('description')), 'thumbnail': str_or_none(self.remove_params(product.get('thumbnailImage'))), 'duration': int_or_none(product.get('duration')), 'timestamp': parse_iso8601(product.get('createdDate')), 'categories': [genre.get('title') for genre in product.get('genres', []) if genre.get('title')], - 'series': str_or_none(series), + 'series': str_or_none(show.get('title')), 'season': int_or_none(season.get('seasonName')), - 'season_number': int_or_none(season_number), + 'season_number': int_or_none(season.get('seasonNumber')), 'season_id': str_or_none(season.get('id')), - 'episode_number': int_or_none(episode_number), + 'episode_number': int_or_none(product.get('number')), 'episode_id': video_id, 'formats': formats, 'subtitles': subtitles, From 1928fac879e6370e36123a80cd2daaf657d90792 Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:02:58 +0000 Subject: [PATCH 07/10] Update shahid.py --- yt_dlp/extractor/shahid.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index 30bae86fa3..ad5656c190 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -31,9 +31,9 @@ class ShahidBaseIE(InfoExtractor): query_string = urllib.parse.urlencode(query_params) api_url = f'{self._API_BASE}/playout/new/url/{video_id}?{query_string}' - return self._download_json(api_url, video_id, note=f'Downloading API JSON for country={country}', fatal=False) + return self._download_json(api_url, video_id, note=f'Downloading API JSON for country={country}') - def get_formats_subtitles(self, video_id, live): + def get_stream_url(self, video_id): geo_bypass_country = self.get_param('geo_bypass_country', None) # Try multiple country codes for geo-unblocking countries = orderedSet((geo_bypass_country, None, 'SA', 'AE', 'EG', 'US')) @@ -48,25 +48,18 @@ class ShahidBaseIE(InfoExtractor): continue if not response: - raise ExtractorError('Unable to get a successful API response for ' + video_id) + raise ExtractorError('Unable to get a playout API response for ' + video_id) playout = response.get('playout', {}) + if not self.get_param('allow_unplayable_formats') and playout.get('drm', False): self.report_drm(video_id) - stream_url = playout.get('url') - if not stream_url: - raise ExtractorError('Stream URL not found in API response.') - - return self._extract_m3u8_formats_and_subtitles(re.sub( - # https://docs.aws.amazon.com/mediapackage/latest/ug/manifest-filtering.html - r'aws\.manifestfilter=[\w:;,-]+&?', - '', stream_url), video_id, 'mp4', live=live) + # https://docs.aws.amazon.com/mediapackage/latest/ug/manifest-filtering.html + return re.sub(r'aws\.manifestfilter=[\w:;,-]+&?', '', playout['url']) def _get_product_info(self, product_id): - return self._call_api('product/id', product_id, { - 'id': product_id, - }) + return self._call_api('product/id', product_id, {'id': product_id}) def remove_params(self, url): if url: @@ -165,13 +158,16 @@ class ShahidIE(ShahidBaseIE): def _real_extract(self, url): video_id = self._match_id(url) - formats, subtitles = self.get_formats_subtitles(video_id, live=False) product_info_response = self._get_product_info(video_id) product = product_info_response.get('productModel', {}) show = product.get('show', {}) season = show.get('season', {}) + formats, subtitles = ( + self._extract_m3u8_formats_and_subtitles( + self.get_stream_url(video_id), video_id, 'mp4', live=True)) + return { 'id': video_id, 'title': str_or_none(product.get('title')), @@ -237,7 +233,9 @@ class ShahidLiveIE(ShahidBaseIE): product_info_response = self._get_product_info(video_id) product = product_info_response.get('productModel', {}) - formats, subtitles = self.get_formats_subtitles(video_id, live=True) + + formats, subtitles = self._extract_m3u8_formats_and_subtitles( + self.get_stream_url(video_id), video_id, 'mp4') return { 'id': video_id, From bd72d47bf26ef921033d4eca4ce2a6770736c045 Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:11:41 +0000 Subject: [PATCH 08/10] Remove all params from stream url --- yt_dlp/extractor/shahid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index ad5656c190..c3684d1dec 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -55,8 +55,8 @@ class ShahidBaseIE(InfoExtractor): if not self.get_param('allow_unplayable_formats') and playout.get('drm', False): self.report_drm(video_id) - # https://docs.aws.amazon.com/mediapackage/latest/ug/manifest-filtering.html - return re.sub(r'aws\.manifestfilter=[\w:;,-]+&?', '', playout['url']) + # Removes quality limiting parameters + return self.remove_params(playout.get('url')) def _get_product_info(self, product_id): return self._call_api('product/id', product_id, {'id': product_id}) From 3649a620517b06df9abd98e432b6dffe1c88aae8 Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:48:53 +0000 Subject: [PATCH 09/10] Remove unused re import --- yt_dlp/extractor/shahid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index c3684d1dec..e118fe8a1a 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -1,6 +1,5 @@ import json import math -import re import urllib.parse from .common import InfoExtractor From 0df89a8efd2792bd32898195b096b3841a5cd4ca Mon Sep 17 00:00:00 2001 From: CasperMcFadden95 <145611964+CasperMcFadden95@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:55:09 +0000 Subject: [PATCH 10/10] Don't hardcode pageSize --- yt_dlp/extractor/shahid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt_dlp/extractor/shahid.py b/yt_dlp/extractor/shahid.py index e118fe8a1a..11430562c0 100644 --- a/yt_dlp/extractor/shahid.py +++ b/yt_dlp/extractor/shahid.py @@ -279,7 +279,7 @@ class ShahidShowIE(ShahidBaseIE): 'product/playlist', show_id, { 'playListId': playlist_id, 'pageNumber': page_num, - 'pageSize': 30, + 'pageSize': self._PAGE_SIZE, 'sorts': [{ 'order': 'DESC', 'type': 'SORTDATE',