diff --git a/db/crud.py b/db/crud.py index cdcec85..d7da58f 100644 --- a/db/crud.py +++ b/db/crud.py @@ -41,7 +41,8 @@ from utils.parser import ( parse_stream_data, parse_tv_stream_data, calculate_max_similarity_ratio, - create_exception_stream + create_exception_stream, + create_content_warning_message, ) from utils.validation_helper import ( validate_parent_guide_nudity, @@ -525,19 +526,16 @@ async def get_movie_streams( ) ] movie_metadata = await get_movie_data_by_id(video_id) - if not (movie_metadata and validate_parent_guide_nudity(movie_metadata, user_data)): - if movie_metadata: - return [create_exception_stream( - f"MediaFusion {user_data.streaming_provider.service.title()}", - f"🚫This item contains inappropriate content and it is advisable not to watch\n" - f"{movie_metadata.parent_guide_nudity_status} {movie_metadata.parent_guide_certificates}", + if not movie_metadata: + return [] + if validate_parent_guide_nudity(movie_metadata, user_data) is False: + return [ + create_exception_stream( + settings.addon_name, + create_content_warning_message(movie_metadata), "inappropriate_content.mp4", - )] - return [create_exception_stream( - f"MediaFusion {user_data.streaming_provider.service.title()}", - "🚫Movie metadata not found", - "metadata_not_found.mp4" - )] + ) + ] live_search_streams = user_data.live_search_streams and video_id.startswith("tt") cache_key = f"torrent_streams:{video_id}" @@ -577,21 +575,16 @@ async def get_series_streams( background_tasks: BackgroundTasks, ) -> list[Stream]: series_metadata = await get_series_data_by_id(video_id) - if not ( - series_metadata and validate_parent_guide_nudity(series_metadata, user_data) - ): - if series_metadata: - return [create_exception_stream( - f"MediaFusion {user_data.streaming_provider.service.title()}", - f"🚫This item contains inappropriate content.\n" - f"{series_metadata.parent_guide_nudity_status} {series_metadata.parent_guide_certificates}", + if not series_metadata: + return [] + if validate_parent_guide_nudity(series_metadata, user_data) is False: + return [ + create_exception_stream( + settings.addon_name, + create_content_warning_message(series_metadata), "inappropriate_content.mp4", - )] - return [create_exception_stream( - f"MediaFusion {user_data.streaming_provider.service.title()}", - "🚫Series metadata not found", - "metadata_not_found.mp4" - )] + ) + ] live_search_streams = user_data.live_search_streams and video_id.startswith("tt") cache_key = f"torrent_streams:{video_id}:{season}:{episode}" diff --git a/resources/exceptions/filtered_no_streams.mp4 b/resources/exceptions/filtered_no_streams.mp4 new file mode 100644 index 0000000..5d89b1f Binary files /dev/null and b/resources/exceptions/filtered_no_streams.mp4 differ diff --git a/resources/exceptions/inappropriate_content.mp4 b/resources/exceptions/inappropriate_content.mp4 new file mode 100644 index 0000000..c738b28 Binary files /dev/null and b/resources/exceptions/inappropriate_content.mp4 differ diff --git a/utils/exception_video.py b/utils/exception_video.py index 327ac60..a276c0e 100644 --- a/utils/exception_video.py +++ b/utils/exception_video.py @@ -23,6 +23,6 @@ def create_text_video( if __name__ == "__main__": create_text_video( - "../resources/exceptions/update_imdb_data.mp4", - "We updated the metadata from IMDb.\nThanks for your contribution!", + "resources/exceptions/inappropriate_content.mp4", + "This content may not be suitable for your preferences", ) diff --git a/utils/parser.py b/utils/parser.py index c136d6a..db0cf4c 100644 --- a/utils/parser.py +++ b/utils/parser.py @@ -22,7 +22,7 @@ from streaming_providers.cache_helpers import ( ) from utils import const from utils.config import config_manager -from utils.const import STREAMING_PROVIDERS_SHORT_NAMES +from utils.const import STREAMING_PROVIDERS_SHORT_NAMES, CERTIFICATION_MAPPING from utils.network import encode_mediaflow_proxy_url from utils.runtime_const import ADULT_CONTENT_KEYWORDS, TRACKERS, MANIFEST_TEMPLATE from utils.validation_helper import validate_m3u8_or_mpd_url_with_cache @@ -92,12 +92,7 @@ async def filter_and_sort_streams( filtered_streams.append(stream) if not filtered_streams: - return [ - create_exception_stream( - settings.addon_name, - "🚫 Streams were found, but they were filtered due to your configuration.", - "filtered_no_streams.mp4" - )] + return [] # Step 2: Update cache status based on provider if user_data.streaming_provider: @@ -229,19 +224,15 @@ async def parse_stream_data( user_ip: str | None = None, is_series: bool = False, ) -> list[Stream]: + if not streams: + return [] + streaming_provider_name = ( STREAMING_PROVIDERS_SHORT_NAMES.get(user_data.streaming_provider.service, "P2P") if user_data.streaming_provider else "P2P" ) - - if not streams: - return [ - create_exception_stream( - settings.addon_name, - "🚫 No results available in the system.", - "no_results.mp4") - ] + addon_name = f"{settings.addon_name} {streaming_provider_name}" stremio_video_id = ( f"{streams[0].meta_id}:{season}:{episode}" if is_series else streams[0].meta_id @@ -250,7 +241,14 @@ async def parse_stream_data( streams, user_data, stremio_video_id, user_ip ) - streams = await filter_and_sort_streams(streams, user_data, user_ip, streaming_provider_name) + if not streams: + return [ + create_exception_stream( + settings.addon_name, + "🚫 Streams Found\nβš™οΈ Filtered by your configuration preferences", + "filtered_no_streams.mp4", + ) + ] # Precompute constant values show_full_torrent_name = user_data.show_full_torrent_name @@ -265,7 +263,7 @@ async def parse_stream_data( user_data.mediaflow_config and user_data.mediaflow_config.proxy_debrid_streams ): - streaming_provider_name += " πŸ•΅πŸΌβ€β™‚οΈ" + addon_name += " πŸ•΅πŸΌβ€β™‚οΈ" base_proxy_url_template = ( f"{settings.host_url}/streaming_provider/{secret_str}/stream/{{}}" @@ -355,7 +353,7 @@ async def parse_stream_data( ) stream_details = { - "name": f"{settings.addon_name} {streaming_provider_name} {resolution} {streaming_provider_status}", + "name": f"{addon_name} {resolution} {streaming_provider_status}", "description": description, "behaviorHints": { "bingeGroup": f"{settings.addon_name.replace(' ', '-')}-{quality_detail}-{resolution}", @@ -655,3 +653,75 @@ def calculate_max_similarity_ratio( max_similarity_ratio = max([title_similarity_ratio] + aka_similarity_ratios) return max_similarity_ratio + + +def get_certification_level(certificates: list) -> str: + """ + Get the highest certification level from a list of certificates. + Returns the category name (All Ages, Children, etc.) based on the highest restriction level. + """ + if not certificates: + return "Unknown" + + # Order of restriction levels from lowest to highest + levels = ["All Ages", "Children", "Parental Guidance", "Teens", "Adults"] + + highest_level = "Unknown" + for certificate in certificates: + for level in levels: + if certificate in CERTIFICATION_MAPPING[level]: + # If current level is more restrictive, update highest_level + if ( + levels.index(level) > levels.index(highest_level) + if highest_level in levels + else -1 + ): + highest_level = level + + return highest_level + + +def get_age_rating_emoji(certification_level: str) -> str: + """ + Get appropriate emoji for certification level + """ + emoji_mapping = { + "All Ages": "πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦", + "Children": "πŸ‘Ά", + "Parental Guidance": "πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦", + "Teens": "πŸ‘±", + "Adults": "πŸ”ž", + "Unknown": "❓", + } + return emoji_mapping.get(certification_level, "❓") + + +def get_nudity_status_emoji(nudity_status: str) -> str: + """ + Get appropriate emoji for nudity status + """ + emoji_mapping = { + "None": "πŸ‘•", + "Mild": "⚠️", + "Moderate": "πŸ”ž", + "Severe": "β›”", + } + return emoji_mapping.get(nudity_status, "❓") + + +def create_content_warning_message(metadata) -> str: + """ + Create a formatted warning message with emojis based on movie metadata + """ + cert_level = get_certification_level(metadata.parent_guide_certificates) + cert_emoji = get_age_rating_emoji(cert_level) + nudity_emoji = get_nudity_status_emoji(metadata.parent_guide_nudity_status) + + message = ( + f"⚠️ Content Warning ⚠️\n" + f"This content may not be suitable for your preferences:\n" + f"Certification: {cert_emoji} {cert_level}\n" + f"Nudity Status: {nudity_emoji} {metadata.parent_guide_nudity_status}" + ) + + return message