diff --git a/exif2gpx.py b/exif2gpx.py new file mode 100644 index 0000000..0c50e2f --- /dev/null +++ b/exif2gpx.py @@ -0,0 +1,218 @@ +#!/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ąć