使用 Python 和 Piexif 将 EXIF GPS 数据添加到 .jpg 文件

Adding EXIF GPS data to .jpg files using Python and Piexif

提问人:Mike Resoli 提问时间:8/31/2023 更新时间:9/7/2023 访问量:259

问:

我正在尝试编写一个脚本,使用 Python 将 EXIF GPS 数据添加到图像中。运行以下脚本时,我收到以下错误:piexif.dump()

(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

有谁知道为什么会发生这种情况?以下是完整的脚本。任何帮助都表示赞赏。

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)
蟒蛇 exif piexif

评论


答:

3赞 TDG 9/6/2023 #1

问题的根源是负经度/纬度值 - 将提供的数据转换为字节格式,而导致错误的行 - 需要无符号值,如 L 参数所示。
这里的讨论中,您可以看到负值在添加到 exif 之前已转换为正值。还发现了这个将负值转换为正值的例子,同时保持右半/半半 - N/S 或 E/W。我不知道为什么该模块不使用负值 - 一些 EXIF 阅读器也会读取像这样的 N/S E/W 值,而另一些阅读器会忽略它 - 就像您通过右键单击图像获得的 Windows 内置阅读器 -> 属性 ->详细信息。
piexifstruct.pack(">L", num)

评论

1赞 Jim Easterbrook 9/6/2023
EXIF标准为纬度和经度指定了无符号的有理数,N/S和E/W值存储为单独的字节/字符。
2赞 Life is complex 9/7/2023 #2

Phil Harvey 的 ExifTool 将处理负坐标,例如 ,但 piexif 存在负坐标问题。-118.2437

代码中的行生成输出。此嵌套元组中的负值在调用此代码行时会导致问题lon_dms = deg_to_dms(longitude)((-118, 1), (-14, 1), (-37, 1))exif_bytes = piexif.dump(exif_dict)

在下面的代码中,负坐标在函数中被删除。该函数生成的值需要转换为可以使用的格式,这是在函数中完成的。deg_to_dmspiexifdms_to_exif_format

下面的代码仍然需要一些额外的错误处理,也许还需要一些日志记录,以便为生产做好准备。

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)

这是没有 GPS 数据的原始图像: enter image description here

以下是带有 GPS 数据的修改图像: enter image description here

这是一个在线实用程序,可用于检查从十进制坐标到 DMS(度、分和秒)坐标的转换。还有一个可以逆转这个过程。