PunimTag Web Application - Major Feature Release #1
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user