|
|
""" |
|
|
Custom Exception Classes |
|
|
Provides specific exception types for better error handling and debugging |
|
|
""" |
|
|
|
|
|
from typing import Optional, Any, Dict |
|
|
|
|
|
class VideoProcessingError(Exception): |
|
|
"""Base exception for video processing errors""" |
|
|
|
|
|
def __init__(self, message: str, error_code: Optional[str] = None, details: Optional[Dict[str, Any]] = None): |
|
|
super().__init__(message) |
|
|
self.message = message |
|
|
self.error_code = error_code |
|
|
self.details = details or {} |
|
|
|
|
|
def __str__(self): |
|
|
base_msg = self.message |
|
|
if self.error_code: |
|
|
base_msg = f"[{self.error_code}] {base_msg}" |
|
|
if self.details: |
|
|
details_str = ", ".join(f"{k}: {v}" for k, v in self.details.items()) |
|
|
base_msg = f"{base_msg} ({details_str})" |
|
|
return base_msg |
|
|
|
|
|
class ModelLoadingError(VideoProcessingError): |
|
|
"""Exception raised when model loading fails""" |
|
|
|
|
|
def __init__(self, model_name: str, message: str, original_error: Optional[Exception] = None): |
|
|
super().__init__( |
|
|
message=f"Failed to load {model_name}: {message}", |
|
|
error_code="MODEL_LOAD_FAILED", |
|
|
details={ |
|
|
"model_name": model_name, |
|
|
"original_error": str(original_error) if original_error else None |
|
|
} |
|
|
) |
|
|
self.model_name = model_name |
|
|
self.original_error = original_error |
|
|
|
|
|
class DeviceError(VideoProcessingError): |
|
|
"""Exception raised when device-related operations fail""" |
|
|
|
|
|
def __init__(self, device_type: str, message: str, available_devices: Optional[list] = None): |
|
|
super().__init__( |
|
|
message=f"Device error ({device_type}): {message}", |
|
|
error_code="DEVICE_ERROR", |
|
|
details={ |
|
|
"device_type": device_type, |
|
|
"available_devices": available_devices |
|
|
} |
|
|
) |
|
|
self.device_type = device_type |
|
|
self.available_devices = available_devices or [] |
|
|
|
|
|
class MemoryError(VideoProcessingError): |
|
|
"""Exception raised when memory operations fail""" |
|
|
|
|
|
def __init__(self, operation: str, message: str, memory_usage: Optional[Dict[str, Any]] = None): |
|
|
super().__init__( |
|
|
message=f"Memory error during {operation}: {message}", |
|
|
error_code="MEMORY_ERROR", |
|
|
details={ |
|
|
"operation": operation, |
|
|
"memory_usage": memory_usage |
|
|
} |
|
|
) |
|
|
self.operation = operation |
|
|
self.memory_usage = memory_usage or {} |
|
|
|
|
|
class VideoFileError(VideoProcessingError): |
|
|
"""Exception raised when video file operations fail""" |
|
|
|
|
|
def __init__(self, file_path: str, operation: str, message: str): |
|
|
super().__init__( |
|
|
message=f"Video file error ({operation}): {message}", |
|
|
error_code="VIDEO_FILE_ERROR", |
|
|
details={ |
|
|
"file_path": file_path, |
|
|
"operation": operation |
|
|
} |
|
|
) |
|
|
self.file_path = file_path |
|
|
self.operation = operation |
|
|
|
|
|
class BackgroundProcessingError(VideoProcessingError): |
|
|
"""Exception raised when background processing fails""" |
|
|
|
|
|
def __init__(self, background_type: str, message: str, background_path: Optional[str] = None): |
|
|
super().__init__( |
|
|
message=f"Background processing error ({background_type}): {message}", |
|
|
error_code="BACKGROUND_ERROR", |
|
|
details={ |
|
|
"background_type": background_type, |
|
|
"background_path": background_path |
|
|
} |
|
|
) |
|
|
self.background_type = background_type |
|
|
self.background_path = background_path |
|
|
|
|
|
class SegmentationError(VideoProcessingError): |
|
|
"""Exception raised when person segmentation fails""" |
|
|
|
|
|
def __init__(self, frame_number: int, message: str, segmentation_method: Optional[str] = None): |
|
|
super().__init__( |
|
|
message=f"Segmentation error at frame {frame_number}: {message}", |
|
|
error_code="SEGMENTATION_ERROR", |
|
|
details={ |
|
|
"frame_number": frame_number, |
|
|
"segmentation_method": segmentation_method |
|
|
} |
|
|
) |
|
|
self.frame_number = frame_number |
|
|
self.segmentation_method = segmentation_method |
|
|
|
|
|
class AudioProcessingError(VideoProcessingError): |
|
|
"""Exception raised when audio processing fails""" |
|
|
|
|
|
def __init__(self, operation: str, message: str, input_file: Optional[str] = None, output_file: Optional[str] = None): |
|
|
super().__init__( |
|
|
message=f"Audio processing error ({operation}): {message}", |
|
|
error_code="AUDIO_ERROR", |
|
|
details={ |
|
|
"operation": operation, |
|
|
"input_file": input_file, |
|
|
"output_file": output_file |
|
|
} |
|
|
) |
|
|
self.operation = operation |
|
|
self.input_file = input_file |
|
|
self.output_file = output_file |
|
|
|
|
|
class ConfigurationError(VideoProcessingError): |
|
|
"""Exception raised when configuration is invalid""" |
|
|
|
|
|
def __init__(self, config_key: str, value: Any, expected: str): |
|
|
super().__init__( |
|
|
message=f"Invalid configuration: {config_key} = {value}, expected {expected}", |
|
|
error_code="CONFIG_ERROR", |
|
|
details={ |
|
|
"config_key": config_key, |
|
|
"value": value, |
|
|
"expected": expected |
|
|
} |
|
|
) |
|
|
self.config_key = config_key |
|
|
self.value = value |
|
|
self.expected = expected |
|
|
|
|
|
class ProcessingCancelledError(VideoProcessingError): |
|
|
"""Exception raised when processing is cancelled by user""" |
|
|
|
|
|
def __init__(self, stage: str, processed_frames: int = 0, total_frames: int = 0): |
|
|
super().__init__( |
|
|
message=f"Processing cancelled at {stage}", |
|
|
error_code="PROCESSING_CANCELLED", |
|
|
details={ |
|
|
"stage": stage, |
|
|
"processed_frames": processed_frames, |
|
|
"total_frames": total_frames, |
|
|
"completion_percentage": (processed_frames / total_frames * 100) if total_frames > 0 else 0 |
|
|
} |
|
|
) |
|
|
self.stage = stage |
|
|
self.processed_frames = processed_frames |
|
|
self.total_frames = total_frames |
|
|
|
|
|
class TwoStageProcessingError(VideoProcessingError): |
|
|
"""Exception raised during two-stage processing""" |
|
|
|
|
|
def __init__(self, stage: str, message: str, chroma_preset: Optional[str] = None): |
|
|
super().__init__( |
|
|
message=f"Two-stage processing error ({stage}): {message}", |
|
|
error_code="TWO_STAGE_ERROR", |
|
|
details={ |
|
|
"stage": stage, |
|
|
"chroma_preset": chroma_preset |
|
|
} |
|
|
) |
|
|
self.stage = stage |
|
|
self.chroma_preset = chroma_preset |
|
|
|
|
|
class ResourceExhaustionError(VideoProcessingError): |
|
|
"""Exception raised when system resources are exhausted""" |
|
|
|
|
|
def __init__(self, resource_type: str, current_usage: float, limit: float, unit: str = ""): |
|
|
super().__init__( |
|
|
message=f"Resource exhaustion: {resource_type} usage {current_usage}{unit} exceeds limit {limit}{unit}", |
|
|
error_code="RESOURCE_EXHAUSTION", |
|
|
details={ |
|
|
"resource_type": resource_type, |
|
|
"current_usage": current_usage, |
|
|
"limit": limit, |
|
|
"unit": unit |
|
|
} |
|
|
) |
|
|
self.resource_type = resource_type |
|
|
self.current_usage = current_usage |
|
|
self.limit = limit |
|
|
self.unit = unit |
|
|
|
|
|
class ValidationError(VideoProcessingError): |
|
|
"""Exception raised when input validation fails""" |
|
|
|
|
|
def __init__(self, validation_type: str, message: str, invalid_value: Any = None): |
|
|
super().__init__( |
|
|
message=f"Validation error ({validation_type}): {message}", |
|
|
error_code="VALIDATION_ERROR", |
|
|
details={ |
|
|
"validation_type": validation_type, |
|
|
"invalid_value": invalid_value |
|
|
} |
|
|
) |
|
|
self.validation_type = validation_type |
|
|
self.invalid_value = invalid_value |
|
|
|
|
|
|
|
|
|
|
|
def handle_processing_error(func): |
|
|
"""Decorator to handle common processing errors""" |
|
|
def wrapper(*args, **kwargs): |
|
|
try: |
|
|
return func(*args, **kwargs) |
|
|
except VideoProcessingError: |
|
|
|
|
|
raise |
|
|
except MemoryError as e: |
|
|
raise MemoryError("memory_operation", str(e)) from e |
|
|
except FileNotFoundError as e: |
|
|
raise VideoFileError(str(e.filename) if e.filename else "unknown", "file_access", str(e)) from e |
|
|
except PermissionError as e: |
|
|
raise VideoFileError(str(e.filename) if e.filename else "unknown", "file_permission", str(e)) from e |
|
|
except Exception as e: |
|
|
raise VideoProcessingError(f"Unexpected error in {func.__name__}: {str(e)}") from e |
|
|
return wrapper |
|
|
|
|
|
def create_error_context(operation: str, **context) -> Dict[str, Any]: |
|
|
"""Create error context dictionary""" |
|
|
return { |
|
|
"operation": operation, |
|
|
"timestamp": __import__('time').time(), |
|
|
**context |
|
|
} |
|
|
|
|
|
def log_error_with_context(logger, error: VideoProcessingError, additional_context: Optional[Dict[str, Any]] = None): |
|
|
"""Log error with full context information""" |
|
|
context = error.details.copy() |
|
|
if additional_context: |
|
|
context.update(additional_context) |
|
|
|
|
|
logger.error(f"{error} | Context: {context}") |
|
|
|
|
|
|
|
|
if hasattr(error, 'original_error') and error.original_error: |
|
|
logger.error(f"Original error: {error.original_error}") |
|
|
|
|
|
def is_recoverable_error(error: Exception) -> bool: |
|
|
"""Determine if an error is potentially recoverable""" |
|
|
recoverable_errors = ( |
|
|
MemoryError, |
|
|
ResourceExhaustionError, |
|
|
DeviceError |
|
|
) |
|
|
|
|
|
|
|
|
if isinstance(error, recoverable_errors): |
|
|
return True |
|
|
|
|
|
|
|
|
if isinstance(error, VideoProcessingError): |
|
|
recoverable_codes = ["MEMORY_ERROR", "DEVICE_ERROR", "RESOURCE_EXHAUSTION"] |
|
|
return error.error_code in recoverable_codes |
|
|
|
|
|
return False |
|
|
|
|
|
def get_error_severity(error: Exception) -> str: |
|
|
"""Get error severity level""" |
|
|
if isinstance(error, ProcessingCancelledError): |
|
|
return "INFO" |
|
|
elif isinstance(error, ValidationError): |
|
|
return "WARNING" |
|
|
elif isinstance(error, (ModelLoadingError, DeviceError)): |
|
|
return "CRITICAL" |
|
|
elif isinstance(error, VideoProcessingError): |
|
|
return "ERROR" |
|
|
else: |
|
|
return "UNKNOWN" |