From d89238facffb71579fbc79e38a45bba74e97d565 Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Fri, 30 Jan 2026 16:31:48 +0000 Subject: [PATCH] refactor: Enhance EXIF date extraction in photo_service - Improved logging for EXIF date extraction process, including success and failure messages. - Updated methods to access EXIF data using modern and deprecated APIs. - Added validation for extracted dates to ensure they fall within a valid range. - Enhanced error handling for various exceptions during EXIF data access and date parsing. --- backend/services/photo_service.py | 93 ++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/backend/services/photo_service.py b/backend/services/photo_service.py index 3bfccef..70cebb4 100644 --- a/backend/services/photo_service.py +++ b/backend/services/photo_service.py @@ -58,29 +58,42 @@ def extract_exif_date(image_path: str) -> Optional[date]: """Extract date taken from photo EXIF data - returns Date (not DateTime) to match desktop schema. Tries multiple methods to extract EXIF date: - 1. PIL's getexif() (modern method) + 1. PIL's getexif() (modern method) - uses .get() for tag access 2. PIL's _getexif() (deprecated but sometimes more reliable) 3. Access EXIF IFD directly if available + + Returns: + Date object or None if no valid EXIF date found """ + import logging + logger = logging.getLogger(__name__) + try: with Image.open(image_path) as image: exifdata = None + is_modern_api = False # Try modern getexif() first try: exifdata = image.getexif() - except Exception: - pass + if exifdata and len(exifdata) > 0: + is_modern_api = True + logger.debug(f"Using modern getexif() API for {image_path}, found {len(exifdata)} EXIF tags") + except Exception as e: + logger.debug(f"Modern getexif() failed for {image_path}: {e}") # If getexif() didn't work or returned empty, try deprecated _getexif() if not exifdata or len(exifdata) == 0: try: if hasattr(image, '_getexif'): exifdata = image._getexif() - except Exception: - pass + if exifdata: + logger.debug(f"Using deprecated _getexif() API for {image_path}, found {len(exifdata)} EXIF tags") + except Exception as e: + logger.debug(f"Deprecated _getexif() failed for {image_path}: {e}") if not exifdata: + logger.debug(f"No EXIF data found in {image_path}") return None # Look for date taken in EXIF tags @@ -91,32 +104,43 @@ def extract_exif_date(image_path: str) -> Optional[date]: 306, # DateTime - file modification date (lowest priority) ] - # Try direct access first + # Try accessing tags - use .get() method for modern API, direct access for old API for tag_id in date_tags: try: - if tag_id in exifdata: - date_str = exifdata[tag_id] - if date_str: - # Parse EXIF date format (YYYY:MM:DD HH:MM:SS) + # Use .get() method for modern Exif object, direct access for dict-like old API + if is_modern_api: + date_str = exifdata.get(tag_id) + else: + # Old _getexif() returns a dict-like object + date_str = exifdata.get(tag_id) if hasattr(exifdata, 'get') else (exifdata[tag_id] if tag_id in exifdata else None) + + if date_str: + # Parse EXIF date format (YYYY:MM:DD HH:MM:SS) + try: + dt = datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S") + extracted_date = dt.date() + # Validate date before returning (reject future dates) + if extracted_date > date.today() or extracted_date < date(1900, 1, 1): + logger.debug(f"Invalid date {extracted_date} from tag {tag_id} in {image_path}") + continue # Skip invalid dates + logger.debug(f"Successfully extracted date {extracted_date} from tag {tag_id} in {image_path}") + return extracted_date + except ValueError: + # Try alternative format try: - dt = datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S") + dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") extracted_date = dt.date() # Validate date before returning (reject future dates) if extracted_date > date.today() or extracted_date < date(1900, 1, 1): + logger.debug(f"Invalid date {extracted_date} from tag {tag_id} in {image_path}") continue # Skip invalid dates + logger.debug(f"Successfully extracted date {extracted_date} from tag {tag_id} in {image_path}") return extracted_date - except ValueError: - # Try alternative format - try: - dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") - extracted_date = dt.date() - # Validate date before returning (reject future dates) - if extracted_date > date.today() or extracted_date < date(1900, 1, 1): - continue # Skip invalid dates - return extracted_date - except ValueError: - continue - except (KeyError, TypeError): + except ValueError as ve: + logger.debug(f"Failed to parse date string '{date_str}' from tag {tag_id} in {image_path}: {ve}") + continue + except (KeyError, TypeError, AttributeError) as e: + logger.debug(f"Error accessing tag {tag_id} in {image_path}: {e}") continue # Try accessing EXIF IFD directly if available (for tags in EXIF IFD like DateTimeOriginal) @@ -125,35 +149,40 @@ def extract_exif_date(image_path: str) -> Optional[date]: # EXIF IFD is at offset 0x8769 exif_ifd = exifdata.get_ifd(0x8769) if exif_ifd: + logger.debug(f"Trying EXIF IFD for {image_path}") for tag_id in date_tags: - if tag_id in exif_ifd: - date_str = exif_ifd[tag_id] + try: + date_str = exif_ifd.get(tag_id) if hasattr(exif_ifd, 'get') else (exif_ifd[tag_id] if tag_id in exif_ifd else None) if date_str: try: dt = datetime.strptime(date_str, "%Y:%m:%d %H:%M:%S") extracted_date = dt.date() - # Validate date before returning (reject future dates) if extracted_date > date.today() or extracted_date < date(1900, 1, 1): - continue # Skip invalid dates + continue + logger.debug(f"Successfully extracted date {extracted_date} from IFD tag {tag_id} in {image_path}") return extracted_date except ValueError: try: dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S") extracted_date = dt.date() - # Validate date before returning (reject future dates) if extracted_date > date.today() or extracted_date < date(1900, 1, 1): - continue # Skip invalid dates + continue + logger.debug(f"Successfully extracted date {extracted_date} from IFD tag {tag_id} in {image_path}") return extracted_date except ValueError: continue - except Exception: - pass + except (KeyError, TypeError, AttributeError): + continue + except Exception as e: + logger.debug(f"Error accessing EXIF IFD for {image_path}: {e}") + + logger.debug(f"No valid date found in EXIF data for {image_path}") except Exception as e: # Log error for debugging (but don't fail the import) import logging logger = logging.getLogger(__name__) - logger.debug(f"Failed to extract EXIF date from {image_path}: {e}") + logger.warning(f"Failed to extract EXIF date from {image_path}: {e}", exc_info=True) return None