#!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse import os from pathlib import Path from datetime import datetime, timezone, timedelta from typing import Optional, Tuple, List from PIL import Image, ExifTags import xml.etree.ElementTree as ET # Mapy tagów EXIF TAGS = {v: k for k, v in ExifTags.TAGS.items()} GPSTAGS = {v: k for k, v in ExifTags.GPSTAGS.items()} # Akceptowane rozszerzenia zdjęć IMAGE_EXTS = {".jpg", ".jpeg", ".JPG", ".JPEG"} def dms_to_decimal(dms, ref) -> Optional[float]: """ Konwersja DMS (na ułamki) -> stopnie dziesiętne. dms: np. ((52,1), (13,1), (1234,100)) => 52° 13' 12.34" ref: 'N'/'S' lub 'E'/'W' """ try: deg = dms[0][0] / dms[0][1] minutes = dms[1][0] / dms[1][1] seconds = dms[2][0] / dms[2][1] dec = deg + minutes / 60.0 + seconds / 3600.0 if ref in ("S", "W"): dec = -dec return dec except Exception: return None def parse_exif_datetime(raw_dt: Optional[str], subsec: Optional[str]) -> Optional[datetime]: """ Parsuje DateTimeOriginal w formacie 'YYYY:MM:DD HH:MM:SS' i składa z sub-sekundami. Zwraca naive datetime (bez strefy) – później ewentualnie przeliczane wg --tz. """ if not raw_dt: return None try: base = datetime.strptime(raw_dt, "%Y:%m:%d %H:%M:%S") if subsec and subsec.isdigit(): # Subsec może mieć różną długość – skracamy/padding do mikrosekund micro = int((subsec + "000000")[:6]) base = base.replace(microsecond=micro) return base except Exception: return None def apply_tz_and_to_utc(dt: datetime, tz_offset: Optional[str]) -> Optional[datetime]: """ Jeśli podano --tz w formacie +HH:MM lub -HH:MM, interpretuj naive dt w tej strefie, a następnie zwróć dt w UTC. Jeśli --tz nie podano, zwróć None (żeby pominąć