"""Quick Sync service for adaptive streaming based on network and system resources""" import psutil import time import logging import requests from typing import Dict, Any, Optional, Tuple from django.core.cache import cache from django.conf import settings logger = logging.getLogger(__name__) class QuickSyncService: """Service for adaptive streaming quality based on network and system resources""" # Quality presets QUALITY_PRESETS = { 'low': { 'bitrate': 64, # kbps 'buffer_size': 5, # seconds 'preload': 'metadata', 'description': 'Low quality - saves bandwidth', }, 'medium': { 'bitrate': 128, # kbps 'buffer_size': 10, # seconds 'preload': 'auto', 'description': 'Medium quality - balanced', }, 'high': { 'bitrate': 256, # kbps 'buffer_size': 15, # seconds 'preload': 'auto', 'description': 'High quality - best experience', }, 'ultra': { 'bitrate': 320, # kbps 'buffer_size': 20, # seconds 'preload': 'auto', 'description': 'Ultra quality - maximum fidelity', }, 'auto': { 'bitrate': 0, # Auto-detect 'buffer_size': 0, # Auto-adjust 'preload': 'auto', 'description': 'Automatic - adapts to connection', }, } # Network speed thresholds (Mbps) SPEED_THRESHOLDS = { 'ultra': 5.0, # 5 Mbps+ 'high': 2.0, # 2-5 Mbps 'medium': 1.0, # 1-2 Mbps 'low': 0.5, # 0.5-1 Mbps } def __init__(self): self.cache_timeout = 300 # 5 minutes def get_system_resources(self) -> Dict[str, Any]: """ Get current system resource usage Returns: Dictionary with CPU, memory, and disk usage """ try: resources = { 'cpu_percent': psutil.cpu_percent(interval=1), 'memory_percent': psutil.virtual_memory().percent, 'memory_available_mb': psutil.virtual_memory().available / (1024 * 1024), 'disk_usage_percent': psutil.disk_usage('/').percent, 'timestamp': time.time(), } # Cache the results cache.set('quick_sync_system_resources', resources, self.cache_timeout) return resources except Exception as e: logger.error(f"Error getting system resources: {e}") return { 'cpu_percent': 50, 'memory_percent': 50, 'memory_available_mb': 1000, 'disk_usage_percent': 50, 'timestamp': time.time(), } def measure_network_speed(self, test_url: str = None, timeout: int = 5) -> float: """ Measure network download speed Args: test_url: URL to download for speed test timeout: Request timeout in seconds Returns: Download speed in Mbps """ # Check cache first cached_speed = cache.get('quick_sync_network_speed') if cached_speed is not None: return cached_speed try: # Use a small test file (1MB) if not test_url: # Use a reliable CDN for speed testing test_url = 'https://speed.cloudflare.com/__down?bytes=1000000' start_time = time.time() response = requests.get(test_url, timeout=timeout, stream=True) # Download 1MB chunk_size = 8192 downloaded = 0 for chunk in response.iter_content(chunk_size=chunk_size): downloaded += len(chunk) if downloaded >= 1000000: # 1MB break elapsed = time.time() - start_time # Calculate speed in Mbps speed_mbps = (downloaded * 8) / (elapsed * 1000000) # Cache the result cache.set('quick_sync_network_speed', speed_mbps, self.cache_timeout) logger.info(f"Network speed measured: {speed_mbps:.2f} Mbps") return speed_mbps except Exception as e: logger.warning(f"Error measuring network speed: {e}") # Return conservative estimate return 2.0 def get_recommended_quality(self, user_preferences: Dict[str, Any] = None) -> Tuple[str, Dict[str, Any]]: """ Get recommended quality based on network speed and system resources Args: user_preferences: User's quick sync preferences Returns: Tuple of (quality_level, quality_settings) """ # Get user preferences if not user_preferences: user_preferences = { 'mode': 'auto', 'prefer_quality': True, 'adapt_to_system': True, } mode = user_preferences.get('mode', 'auto') # If manual mode, return the specified quality if mode != 'auto': return mode, self.QUALITY_PRESETS[mode] # Auto mode - detect optimal quality network_speed = self.measure_network_speed() system_resources = self.get_system_resources() # Determine quality based on network speed if network_speed >= self.SPEED_THRESHOLDS['ultra']: quality = 'ultra' elif network_speed >= self.SPEED_THRESHOLDS['high']: quality = 'high' elif network_speed >= self.SPEED_THRESHOLDS['medium']: quality = 'medium' else: quality = 'low' # Adjust based on system resources if enabled if user_preferences.get('adapt_to_system', True): cpu_percent = system_resources.get('cpu_percent', 50) memory_percent = system_resources.get('memory_percent', 50) # Downgrade quality if system is under heavy load if cpu_percent > 80 or memory_percent > 85: if quality == 'ultra': quality = 'high' elif quality == 'high': quality = 'medium' # Upgrade if system has plenty of resources and user prefers quality elif cpu_percent < 30 and memory_percent < 50 and user_preferences.get('prefer_quality', False): if quality == 'medium' and network_speed >= self.SPEED_THRESHOLDS['high']: quality = 'high' elif quality == 'high' and network_speed >= self.SPEED_THRESHOLDS['ultra']: quality = 'ultra' settings = self.QUALITY_PRESETS[quality].copy() settings['auto_selected'] = True settings['network_speed_mbps'] = network_speed settings['cpu_percent'] = system_resources.get('cpu_percent') settings['memory_percent'] = system_resources.get('memory_percent') logger.info(f"Recommended quality: {quality} (speed: {network_speed:.2f} Mbps)") return quality, settings def get_buffer_settings(self, quality: str, network_speed: float = None) -> Dict[str, Any]: """ Get optimal buffer settings for quality level Args: quality: Quality level network_speed: Current network speed in Mbps Returns: Buffer configuration """ base_settings = self.QUALITY_PRESETS.get(quality, self.QUALITY_PRESETS['medium']) buffer_config = { 'buffer_size': base_settings['buffer_size'], 'preload': base_settings['preload'], 'max_buffer_size': base_settings['buffer_size'] * 2, 'rebuffer_threshold': base_settings['buffer_size'] * 0.3, } # Adjust based on network speed if provided if network_speed: if network_speed < 1.0: # Slow connection - increase buffer buffer_config['buffer_size'] = max(buffer_config['buffer_size'], 15) buffer_config['preload'] = 'auto' elif network_speed > 5.0: # Fast connection - can use smaller buffer buffer_config['buffer_size'] = min(buffer_config['buffer_size'], 10) return buffer_config def get_quick_sync_status(self, user_preferences: Dict[str, Any] = None) -> Dict[str, Any]: """ Get complete quick sync status and recommendations Args: user_preferences: User's quick sync preferences Returns: Complete status including network, system, and quality info """ network_speed = self.measure_network_speed() system_resources = self.get_system_resources() quality, quality_settings = self.get_recommended_quality(user_preferences) buffer_settings = self.get_buffer_settings(quality, network_speed) status = { 'network': { 'speed_mbps': network_speed, 'status': self._get_network_status(network_speed), }, 'system': { 'cpu_percent': system_resources['cpu_percent'], 'memory_percent': system_resources['memory_percent'], 'memory_available_mb': system_resources['memory_available_mb'], 'status': self._get_system_status(system_resources), }, 'quality': { 'level': quality, 'bitrate': quality_settings['bitrate'], 'description': quality_settings['description'], 'auto_selected': quality_settings.get('auto_selected', False), }, 'buffer': buffer_settings, 'timestamp': time.time(), } return status def _get_network_status(self, speed_mbps: float) -> str: """Get network status description""" if speed_mbps >= 5.0: return 'excellent' elif speed_mbps >= 2.0: return 'good' elif speed_mbps >= 1.0: return 'fair' else: return 'poor' def _get_system_status(self, resources: Dict[str, Any]) -> str: """Get system status description""" cpu = resources.get('cpu_percent', 50) memory = resources.get('memory_percent', 50) if cpu > 80 or memory > 85: return 'high_load' elif cpu > 50 or memory > 70: return 'moderate_load' else: return 'low_load' def update_user_preferences(self, user_id: int, preferences: Dict[str, Any]) -> bool: """ Update user's quick sync preferences Args: user_id: User ID preferences: New preferences Returns: True if successful """ try: # Validate preferences mode = preferences.get('mode', 'auto') if mode not in self.QUALITY_PRESETS: mode = 'auto' prefs = { 'mode': mode, 'prefer_quality': preferences.get('prefer_quality', True), 'adapt_to_system': preferences.get('adapt_to_system', True), 'auto_download_quality': preferences.get('auto_download_quality', False), } # Cache user preferences cache.set(f'quick_sync_prefs_{user_id}', prefs, timeout=None) logger.info(f"Updated quick sync preferences for user {user_id}: {prefs}") return True except Exception as e: logger.error(f"Error updating quick sync preferences: {e}") return False def get_user_preferences(self, user_id: int) -> Dict[str, Any]: """ Get user's quick sync preferences Args: user_id: User ID Returns: User preferences or defaults """ prefs = cache.get(f'quick_sync_prefs_{user_id}') if prefs: return prefs # Return defaults return { 'mode': 'auto', 'prefer_quality': True, 'adapt_to_system': True, 'auto_download_quality': False, }