hMatoba / Piexif

Exif manipulation with pure python script.
MIT License
367 stars 81 forks source link

UnboundLocalError: local variable 'new_value' referenced before assignment #131

Open djjudas21 opened 2 years ago

djjudas21 commented 2 years ago

I've got the following code:

image_metadata = {"0th": encoded_ifd, "Exif": encoded_exif, "GPS": encoded_gps}
pp.pprint(image_metadata)
exif_bytes = piexif.dump(image_metadata)
piexif.insert(exif_bytes, file)

It outputs this:

{'0th': {271: 'Canon',
         272: 'A-1',
         315: 'Jonathan Gazeley',
         33432: '© 2021 Jonathan Gazeley'},
 'Exif': {34850: 3,
          34867: 100,
          36867: '2021-08-26T22:01:00Z',
          37377: '0.00080000',
          37381: '2.0',
          37383: 2,
          37386: '50.0',
          41989: '50.0',
          42016: '1d580fb5-a40f-46e2-abb0-9ee658dcc0e3',
          42033: '12345',
          42035: 'Canon',
          42036: 'FD 50mm f/1.8',
          42037: '22222'},
 'GPS': {}}
Traceback (most recent call last):
  File "/home/jonathan/git/camerahub/tagger-cli/main.py", line 169, in <module>
    exif_bytes = piexif.dump(image_metadata)
  File "/home/jonathan/.cache/pypoetry/virtualenvs/tagger-cli-SSoXMnv6-py3.10/lib/python3.10/site-packages/piexif/_dump.py", line 68, in dump
    exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length)
  File "/home/jonathan/.cache/pypoetry/virtualenvs/tagger-cli-SSoXMnv6-py3.10/lib/python3.10/site-packages/piexif/_dump.py", line 335, in _dict_to_bytes
    length_str, value_str, four_bytes_over = _value_to_bytes(raw_value,
  File "/home/jonathan/.cache/pypoetry/virtualenvs/tagger-cli-SSoXMnv6-py3.10/lib/python3.10/site-packages/piexif/_dump.py", line 261, in _value_to_bytes
    four_bytes_over = new_value
UnboundLocalError: local variable 'new_value' referenced before assignment

Is this a bug in piexif or am I using it wrong? Piexif 1.1.3 on Python 3.10.2.

wpl123 commented 2 years ago

I get the same error on Python 3.8.10 with piexif 1.1.3 using code taken directly from the repository example exif.py

gps_ifd = { piexif.GPSIFD.GPSVersionID: (2, 0, 0, 0), piexif.GPSIFD.GPSAltitudeRef: 0, piexif.GPSIFD.GPSAltitude: ele, piexif.GPSIFD.GPSLatitudeRef: lat_ref, piexif.GPSIFD.GPSLatitude: (lat_deg, lat_min, lat_sec), piexif.GPSIFD.GPSLongitudeRef: lon_ref, piexif.GPSIFD.GPSLongitude: (lon_deg, lon_min, lon_sec), }

exif_dict = {"GPS": gps_ifd} exif_bytes = piexif.dump(exif_dict) piexif.insert(exif_bytes, fpath)

Traceback (most recent call last): File "/home/wplaird/Documents/Middle Creek/Maps/GIS/GoogleMaps/Flyover/src/app/make_layer_files.py", line 291, in updated = update_images(df_img_files,df_points) File "/home/wplaird/Documents/Middle Creek/Maps/GIS/GoogleMaps/Flyover/src/app/make_layer_files.py", line 193, in update_images exif_bytes = piexif.dump(exif_dict) File "/home/wplaird/Documents/Middle Creek/Maps/GIS/GoogleMaps/Flyover/src/venv/lib/python3.8/site-packages/piexif/_dump.py", line 74, in dump gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length) File "/home/wplaird/Documents/Middle Creek/Maps/GIS/GoogleMaps/Flyover/src/venv/lib/python3.8/site-packages/piexif/_dump.py", line 335, in _dict_to_bytes length_str, value_str, four_bytes_over = _value_to_bytes(raw_value, File "/home/wplaird/Documents/Middle Creek/Maps/GIS/GoogleMaps/Flyover/src/venv/lib/python3.8/site-packages/piexif/_dump.py", line 247, in _value_to_bytes four_bytes_over = new_value UnboundLocalError: local variable 'new_value' referenced before assignment

aaronwmorris commented 1 year ago

I think I figured this out. The root problem is piexif has terrible error checking.

This error means a value you passed, likely an integer or float, needs to be expressed as a Rational (or SRational).

For instance, ExposureTime is a rational and must be passed like (1, 300) which translates to 1/300s.

In the case of GPS, piexif.GPSIFD.GPSLatitude and piexif.GPSIFD.GPSLongitude values need to be passed as 3 rational numbers... ((84, 1), (30, 1), (1, 2))

djjudas21 commented 1 year ago

Nice one @aaronwmorris. I gave up on piexif ages ago because of this (and other) issues, but this week I started using pyexiv2 and found that it too handles rationals in this way. I've already got functions in my code for converting decimal GPS to degrees/minutes/seconds GPS, so I'm going to tweak that to return rationals instead. Thanks for the tip.

djjudas21 commented 1 year ago

Here's what I've come up with. Note that it returns the 3 rationals as a single formatted string, as required for pyexiv2, e.g. 'Exif.GPSInfo.GPSLatitude': '51/1 27/1 3148/100'

def deg_to_dms(decdegrees):
    """
    Convert from decimal degrees to degrees, minutes, seconds.
    """
    decdegrees = Decimal(decdegrees)
    # Multiply degrees up 3600 to get integer second resolution, divide by 60 to get mins and secs
    mins, secs = divmod(abs(decdegrees)*3600, 60)
    # Further divide by 60 to get degrees and mins
    degs, mins = divmod(mins, 60)
    # round down degs and mins. Secs remains a float
    degs, mins = int(degs), int(mins)
    return degs, mins, secs

def deg_to_dms_rational(decdegrees):
    """
    Convert from decimal degrees to degrees, minutes, seconds expressed as rationals
    This returns 3 rationals formatted as a string suitable for pyexiv2, e.g.
    'Exif.GPSInfo.GPSLatitude': '51/1 27/1 3148/100'
    """
    (degs, mins, secs) = deg_to_dms(decdegrees)
    # As secs is a float, we multiply by 100 for increased precision in the rational
    roundedsecs = round(secs * 100)
    return f"{degs}/1 {mins}/1 {roundedsecs}/100"