PunimTag Web Application - Major Feature Release #1

Open
tanyar09 wants to merge 106 commits from dev into master
Showing only changes of commit 92c7712973 - Show all commits

View File

@ -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