Spaces:
Sleeping
Sleeping
| """Local EXIF GPS extraction to prefill the map pin (no pixels touched).""" | |
| from typing import Optional, Tuple | |
| def _to_degrees(tag): | |
| try: | |
| d, m, s = (float(p.num) / float(p.den) for p in tag.values) | |
| return d + m / 60.0 + s / 3600.0 | |
| except (AttributeError, TypeError, ValueError, ZeroDivisionError): | |
| return None | |
| def extract_gps(path) -> Optional[Tuple[float, float]]: | |
| try: | |
| import exifread | |
| except ImportError: | |
| return None | |
| try: | |
| with open(path, "rb") as fh: | |
| tags = exifread.process_file(fh, details=False) | |
| except Exception: | |
| return None | |
| lat = _to_degrees(tags.get("GPS GPSLatitude")) | |
| lng = _to_degrees(tags.get("GPS GPSLongitude")) | |
| if lat is None or lng is None: | |
| return None | |
| if str(tags.get("GPS GPSLatitudeRef", "N")).strip().upper().startswith("S"): | |
| lat = -lat | |
| if str(tags.get("GPS GPSLongitudeRef", "E")).strip().upper().startswith("W"): | |
| lng = -lng | |
| if not (-90 <= lat <= 90 and -180 <= lng <= 180): | |
| return None | |
| return (lat, lng) | |
| def extract_month(path) -> Optional[int]: | |
| """Calendar month (1-12) from the EXIF capture date, or None.""" | |
| try: | |
| import exifread | |
| except ImportError: | |
| return None | |
| try: | |
| with open(path, "rb") as fh: | |
| tags = exifread.process_file(fh, details=False) | |
| except Exception: | |
| return None | |
| for key in ("EXIF DateTimeOriginal", "EXIF DateTimeDigitized", "Image DateTime"): | |
| raw = str(tags.get(key, "")).strip() | |
| if not raw: | |
| continue | |
| date_part = raw.split()[0] # "YYYY:MM:DD" or "YYYY-MM-DD" | |
| bits = date_part.replace("-", ":").split(":") | |
| if len(bits) >= 2: | |
| try: | |
| month = int(bits[1]) | |
| except ValueError: | |
| continue | |
| if 1 <= month <= 12: | |
| return month | |
| return None | |