66import json
77import logging
88import math
9+ from fractions import Fraction
910from pathlib import Path
1011
1112import piexif
@@ -29,16 +30,19 @@ def __init__(self, filename_or_bytes: Path | bytes) -> None:
2930
3031 @staticmethod
3132 def decimal_to_dms (
32- value : float , precision : int
33- ) -> tuple [tuple [float , int ], tuple [float , int ], tuple [float , int ]]:
34- """
35- Convert decimal position to degrees, minutes, seconds in a fromat supported by EXIF
36- """
37- deg = math .floor (value )
38- min = math .floor ((value - deg ) * 60 )
39- sec = math .floor ((value - deg - min / 60 ) * 3600 * precision )
40-
41- return (deg , 1 ), (min , 1 ), (sec , precision )
33+ value : float ,
34+ ) -> tuple [tuple [int , int ], tuple [int , int ], tuple [int , int ]]:
35+ """Convert decimal position to Exif degrees, minutes, and seconds rationals"""
36+
37+ deg : int = int (value )
38+ min : int = int (value := (value - deg ) * 60 )
39+ sec : float = (value - min ) * 60
40+
41+ return (
42+ (deg , 1 ),
43+ (min , 1 ),
44+ (Fraction .from_float (sec ).limit_denominator ().as_integer_ratio ()),
45+ )
4246
4347 def add_image_description (self , data : dict ) -> None :
4448 """Add a dict to image description."""
@@ -83,41 +87,69 @@ def add_gps_datetime(self, dt: datetime.datetime) -> None:
8387 self ._ef ["GPS" ][piexif .GPSIFD .GPSTimeStamp ] = (
8488 (dt .hour , 1 ),
8589 (dt .minute , 1 ),
86- # num / den = (dt.second * 1e6 + dt.microsecond) / 1e6
87- (int (dt .second * 1e6 + dt .microsecond ), int (1e6 )),
90+ (
91+ Fraction .from_float (dt .second + dt .microsecond / 1e6 )
92+ .limit_denominator ()
93+ .as_integer_ratio ()
94+ ),
8895 )
89-
90- def add_lat_lon (self , lat : float , lon : float , precision : float = 1e7 ) -> None :
96+ if LOG .isEnabledFor (logging .DEBUG ):
97+ LOG .debug (
98+ 'GPSDateStamp: "%s"\t GPSTimeStamp: %s' ,
99+ self ._ef ["GPS" ][piexif .GPSIFD .GPSDateStamp ],
100+ self ._ef ["GPS" ][piexif .GPSIFD .GPSTimeStamp ],
101+ )
102+
103+ def add_lat_lon (self , lat : float , lon : float ) -> None :
91104 """Add lat, lon to gps (lat, lon in float)."""
105+
92106 self ._ef ["GPS" ][piexif .GPSIFD .GPSLatitudeRef ] = "N" if lat > 0 else "S"
107+ self ._ef ["GPS" ][piexif .GPSIFD .GPSLatitude ] = ExifEdit .decimal_to_dms (
108+ math .fabs (lat )
109+ )
93110 self ._ef ["GPS" ][piexif .GPSIFD .GPSLongitudeRef ] = "E" if lon > 0 else "W"
94111 self ._ef ["GPS" ][piexif .GPSIFD .GPSLongitude ] = ExifEdit .decimal_to_dms (
95- abs (lon ), int (precision )
96- )
97- self ._ef ["GPS" ][piexif .GPSIFD .GPSLatitude ] = ExifEdit .decimal_to_dms (
98- abs (lat ), int (precision )
112+ math .fabs (lon )
99113 )
114+ if LOG .isEnabledFor (logging .DEBUG ):
115+ LOG .debug (
116+ "GPSLatitude: %s\t GPSLongitude: %s" ,
117+ self ._ef ["GPS" ][piexif .GPSIFD .GPSLatitude ],
118+ self ._ef ["GPS" ][piexif .GPSIFD .GPSLongitude ],
119+ )
120+
121+ def add_altitude (self , altitude : float ) -> None :
122+ """Add altitude."""
100123
101- def add_altitude (self , altitude : float , precision : int = 100 ) -> None :
102- """Add altitude (pre is the precision)."""
103124 ref = 0 if altitude > 0 else 1
104125 self ._ef ["GPS" ][piexif .GPSIFD .GPSAltitude ] = (
105- int (abs (altitude ) * precision ),
106- precision ,
126+ Fraction .from_float (math .fabs (altitude ))
127+ .limit_denominator ()
128+ .as_integer_ratio ()
107129 )
108130 self ._ef ["GPS" ][piexif .GPSIFD .GPSAltitudeRef ] = ref
109-
110- def add_direction (
111- self , direction : float , ref : str = "T" , precision : int = 100
112- ) -> None :
131+ if LOG .isEnabledFor (logging .DEBUG ):
132+ LOG .debug (
133+ 'GPSAltitudeRef: "%s"\t GPSAltitude: %s' ,
134+ self ._ef ["GPS" ][piexif .GPSIFD .GPSAltitudeRef ],
135+ self ._ef ["GPS" ][piexif .GPSIFD .GPSAltitude ],
136+ )
137+
138+ def add_direction (self , direction : float , ref : str = "T" ) -> None :
113139 """Add image direction."""
140+
114141 # normalize direction
115- direction = direction % 360.0
142+ direction = math . fmod ( direction , 360.0 )
116143 self ._ef ["GPS" ][piexif .GPSIFD .GPSImgDirection ] = (
117- int (abs (direction ) * precision ),
118- precision ,
144+ Fraction .from_float (direction ).limit_denominator ().as_integer_ratio ()
119145 )
120146 self ._ef ["GPS" ][piexif .GPSIFD .GPSImgDirectionRef ] = ref
147+ if LOG .isEnabledFor (logging .DEBUG ):
148+ LOG .debug (
149+ 'GPSImgDirectionRef: "%s"\t GPSImgDirection: %s' ,
150+ self ._ef ["GPS" ][piexif .GPSIFD .GPSImgDirectionRef ],
151+ self ._ef ["GPS" ][piexif .GPSIFD .GPSImgDirection ],
152+ )
121153
122154 def add_make (self , make : str ) -> None :
123155 if not make :
0 commit comments