libvips / pyvips

python binding for libvips using cffi
MIT License
644 stars 49 forks source link

exif gps coordinates / rational64u #408

Open AntiSol opened 1 year ago

AntiSol commented 1 year ago

Hi @jcupitt , thanks for libvips :)

Is there a way for me to get the numeric values for GPS coordinates stored in exif data?

I'm trying to use pyvips to work with gps data stored in exif tags on a bunch of images.

#!/usr/bin/env python3
from pyvips import Image

img = Image.new_from_file('test.jpg')

fields = img.get_fields()

#print(fields) # includes: 'exif-ifd3-GPSVersionID', 'exif-ifd3-GPSLatitudeRef', 'exif-ifd3-GPSLatitude', 'exif-ifd3-GPSLongitudeRef', 'exif-ifd3-GPSLongitude', 'exif-ifd3-GPSAltitude'

lat = img.get('exif-ifd3-GPSLatitude')
print(lat)
print(type(lat))

for https://antisol.org/test.jpg, this gives me:

38/1 26/1 9157/500 (38, 26, 18.314, Rational, 3 components, 24 bytes)
<class 'str'>

I'd rather not have to parse this string into a numeric value into something I can use - is there a way for me to get pyvips to give me the numeric values?

I found This explanation that the coordinates are stored as rational numbers in the exif data. If I could get the numerators and denominators as e.g a list of tuples (I ultimately want to convert to and work with a decimal degrees float) that would be great.

I think maybe I have to use GObject and/or GValue to get this data in a format I can use? I looked into those a little but the documentation seems a little sparse.

Advice much appreciated! Thanks!

jcupitt commented 1 year ago

Hi @AntiSol,

libvips goes to and from string for all exif values (except thumbnails), unfortunately, so you'll need to parse it. libvips has a parser for these strings, of course, but it's not exposed in the API.

This should really be revised -- the libvips exif metadata system was designed way back in the 90s and could use some fixing up. But until then, sorry, it's parsing.

AntiSol commented 1 year ago

thanks for the quick reply!

I'm thinking maybe as a workaround to get what I want I can use image.get('exif-data') (which seems to be the whole exif header as binary data?) and pass that into some dedicated exif library.

AntiSol commented 1 year ago

(feel free to close this if you want, or leave it open if you do plan to update one day, or whatever :) )

jcupitt commented 1 year ago

image.get('exif-data') (which seems to be the whole exif header as binary data?) and pass that into some dedicated exif library.

Yes, that'd be a good workaround.

AntiSol commented 1 year ago

For anyone else trying to solve this problem, here's how you do it with the exif pip package:

from pyvips import Image
from exif._app1_metadata import App1MetaData
from exif._constants import ExifMarkers
from plum.bigendian import uint16

filename='/path/to/file'

image = Image.new_from_file(filename)
# exif data
ed = image.get('exif-data')
#libvips is removing the marker and the length from the data,
# but the exif lib expects them, so we generate them
exif = App1MetaData(ExifMarkers.APP1 + uint16.pack(len(ed)) + ed)                   
# see https://gitlab.com/TNThieding/exif/-/blob/master/src/exif/_app1_metadata.py
tags = exif.get_tag_list()
# use getattr to get tag values, but not all datatypes are implemented, so try/catch
for tag in tags:
  try:
    val = getattr(exif,tag)
    print(f"{tag}: {val}")
  except Exception as ex: 
    print(f"couldn't read '{tag}': {ex}")