I am trying to write a script that adds EXIF GPS data to images using Python. When running the below script, I am getting an error returned from the piexif.dump()
as follows:
(venv) C:\projects\geo-photo>python test2.py
Traceback (most recent call last):
File "C:\projects\geo-photo\test2.py", line 31, in <module>
add_geolocation(image_path, latitude, longitude)
File "C:\projects\geo-photo\test2.py", line 21, in add_geolocation
exif_bytes = piexif.dump(exif_dict)
^^^^^^^^^^^^^^^^^^^^^^
File "C:\projects\geo-photo\venv\Lib\site-packages\piexif\_dump.py", line 74, in dump
gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\projects\geo-photo\venv\Lib\site-packages\piexif\_dump.py", line 335, in _dict_to_bytes
length_str, value_str, four_bytes_over = _value_to_bytes(raw_value,
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\projects\geo-photo\venv\Lib\site-packages\piexif\_dump.py", line 244, in _value_to_bytes
new_value += (struct.pack(">L", num) +
struct.error: argument out of range
Does anyone have any idea as to why this would be happening? Below is the full script. Any help appreciated.
import piexif
def add_geolocation(image_path, latitude, longitude):
exif_dict = piexif.load(image_path)
# Convert latitude and longitude to degrees, minutes, seconds format
def deg_to_dms(deg):
d = int(deg)
m = int((deg - d) * 60)
s = int(((deg - d) * 60 - m) * 60)
return ((d, 1), (m, 1), (s, 1))
lat_dms = deg_to_dms(latitude)
lon_dms = deg_to_dms(longitude)
exif_dict["GPS"][piexif.GPSIFD.GPSLatitude] = lat_dms
exif_dict["GPS"][piexif.GPSIFD.GPSLongitude] = lon_dms
exif_dict["GPS"][piexif.GPSIFD.GPSLatitudeRef] = 'N' if latitude >= 0 else 'S'
exif_dict["GPS"][piexif.GPSIFD.GPSLongitudeRef] = 'E' if longitude >= 0 else 'W'
exif_bytes = piexif.dump(exif_dict)
piexif.insert(exif_bytes, image_path)
print("Geolocation data added to", image_path)
# Example usage
latitude = 34.0522 # Example latitude coordinates
longitude = -118.2437 # Example longitude coordinates
image_path = 'test.jpg' # Path to your image
add_geolocation(image_path, latitude, longitude)
ExifTool by Phil Harvey will handle negative coordinates, such as -118.2437
, but piexif has an issue with negative coordinates.
The line lon_dms = deg_to_dms(longitude)
in your code produces the output ((-118, 1), (-14, 1), (-37, 1))
. The negative values in this nested tuple cause a problem when calling this line of code exif_bytes = piexif.dump(exif_dict)
In the code below the negative coordinates are removed in the function deg_to_dms
. The values that are produced by that function need to be converted into a format that piexif
can use, which is accomplished in the function dms_to_exif_format
.
The code below still needs some additional error handling and maybe some logging to be more production ready.
import piexif
from fractions import Fraction
def deg_to_dms(decimal_coordinate, cardinal_directions):
"""
This function converts decimal coordinates into the DMS (degrees, minutes and seconds) format.
It also determines the cardinal direction of the coordinates.
:param decimal_coordinate: the decimal coordinates, such as 34.0522
:param cardinal_directions: the locations of the decimal coordinate, such as ["S", "N"] or ["W", "E"]
:return: degrees, minutes, seconds and compass_direction
:rtype: int, int, float, string
"""
if decimal_coordinate < 0:
compass_direction = cardinal_directions[0]
elif decimal_coordinate > 0:
compass_direction = cardinal_directions[1]
else:
compass_direction = ""
degrees = int(abs(decimal_coordinate))
decimal_minutes = (abs(decimal_coordinate) - degrees) * 60
minutes = int(decimal_minutes)
seconds = Fraction((decimal_minutes - minutes) * 60).limit_denominator(100)
return degrees, minutes, seconds, compass_direction
def dms_to_exif_format(dms_degrees, dms_minutes, dms_seconds):
"""
This function converts DMS (degrees, minutes and seconds) to values that can
be used with the EXIF (Exchangeable Image File Format).
:param dms_degrees: int value for degrees
:param dms_minutes: int value for minutes
:param dms_seconds: fractions.Fraction value for seconds
:return: EXIF values for the provided DMS values
:rtype: nested tuple
"""
exif_format = (
(dms_degrees, 1),
(dms_minutes, 1),
(int(dms_seconds.limit_denominator(100).numerator), int(dms_seconds.limit_denominator(100).denominator))
)
return exif_format
def add_geolocation(image_path, latitude, longitude):
"""
This function adds GPS values to an image using the EXIF format.
This fumction calls the functions deg_to_dms and dms_to_exif_format.
:param image_path: image to add the GPS data to
:param latitude: the north–south position coordinate
:param longitude: the east–west position coordinate
"""
# converts the latitude and longitude coordinates to DMS
latitude_dms = deg_to_dms(latitude, ["S", "N"])
longitude_dms = deg_to_dms(longitude, ["W", "E"])
# convert the DMS values to EXIF values
exif_latitude = dms_to_exif_format(latitude_dms[0], latitude_dms[1], latitude_dms[2])
exif_longitude = dms_to_exif_format(longitude_dms[0], longitude_dms[1], longitude_dms[2])
try:
# Load existing EXIF data
exif_data = piexif.load(image_path)
# https://exiftool.org/TagNames/GPS.html
# Create the GPS EXIF data
coordinates = {
piexif.GPSIFD.GPSVersionID: (2, 0, 0, 0),
piexif.GPSIFD.GPSLatitude: exif_latitude,
piexif.GPSIFD.GPSLatitudeRef: latitude_dms[3],
piexif.GPSIFD.GPSLongitude: exif_longitude,
piexif.GPSIFD.GPSLongitudeRef: longitude_dms[3]
}
# Update the EXIF data with the GPS information
exif_data['GPS'] = coordinates
# Dump the updated EXIF data and insert it into the image
exif_bytes = piexif.dump(exif_data)
piexif.insert(exif_bytes, image_path)
print(f"EXIF data updated successfully for the image {image_path}.")
except Exception as e:
print(f"Error: {str(e)}")
latitude = 34.0522
longitude = -118.2437
image_path = '_DSC0075.jpeg' # Path to your image
add_geolocation(image_path, latitude, longitude)
Here is the original image without the GPS data:
Here is the modified image with the GPS data:
Here is an online utility that is useful for checking conversions from decimal coordinates to DMS (degrees, minutes and seconds) ones. There is also one that reverses the process.