mirror of
https://forge.chapril.org/tykayn/mapillary_download
synced 2025-06-20 01:34:43 +02:00
Write exif metadata (Quick &Dirty)
This commit is contained in:
parent
a02ca67360
commit
c79ba8a65d
15 changed files with 3771 additions and 4 deletions
227
lib/exif_pil.py
Normal file
227
lib/exif_pil.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
import datetime
|
||||
import struct # Only to catch struct.error due to error in PIL / Pillow.
|
||||
from PIL import Image
|
||||
from PIL.ExifTags import TAGS, GPSTAGS
|
||||
|
||||
# Original: https://gist.github.com/erans/983821
|
||||
# License: MIT
|
||||
# Credits: https://gist.github.com/erans
|
||||
|
||||
|
||||
class ExifException(Exception):
|
||||
def __init__(self, message):
|
||||
self._message = message
|
||||
|
||||
def __str__(self):
|
||||
return self._message
|
||||
|
||||
|
||||
class PILExifReader:
|
||||
def __init__(self, filepath):
|
||||
self._filepath = filepath
|
||||
image = Image.open(filepath)
|
||||
self._exif = self.get_exif_data(image)
|
||||
image.close()
|
||||
|
||||
def get_exif_data(self, image):
|
||||
"""Returns a dictionary from the exif data of an PIL Image
|
||||
item. Also converts the GPS Tags"""
|
||||
exif_data = {}
|
||||
try:
|
||||
info = image._getexif()
|
||||
except OverflowError, e:
|
||||
if e.message == "cannot fit 'long' into an index-sized integer":
|
||||
# Error in PIL when exif data is corrupt.
|
||||
return None
|
||||
else:
|
||||
raise e
|
||||
except struct.error as e:
|
||||
if e.message == "unpack requires a string argument of length 2":
|
||||
# Error in PIL when exif data is corrupt.
|
||||
return None
|
||||
else:
|
||||
raise e
|
||||
if info:
|
||||
for tag, value in info.items():
|
||||
decoded = TAGS.get(tag, tag)
|
||||
if decoded == "GPSInfo":
|
||||
gps_data = {}
|
||||
for t in value:
|
||||
sub_decoded = GPSTAGS.get(t, t)
|
||||
gps_data[sub_decoded] = value[t]
|
||||
exif_data[decoded] = gps_data
|
||||
else:
|
||||
exif_data[decoded] = value
|
||||
return exif_data
|
||||
|
||||
def read_capture_time(self):
|
||||
time_tag = "DateTimeOriginal"
|
||||
|
||||
# read and format capture time
|
||||
if self._exif == None:
|
||||
print "Exif is none."
|
||||
if time_tag in self._exif:
|
||||
capture_time = self._exif[time_tag]
|
||||
capture_time = capture_time.replace(" ","_")
|
||||
capture_time = capture_time.replace(":","_")
|
||||
else:
|
||||
print "No time tag in "+self._filepath
|
||||
capture_time = 0
|
||||
|
||||
# return as datetime object
|
||||
return datetime.datetime.strptime(capture_time, '%Y_%m_%d_%H_%M_%S')
|
||||
|
||||
def _get_if_exist(self, data, key):
|
||||
if key in data:
|
||||
return data[key]
|
||||
else:
|
||||
return None
|
||||
|
||||
def _convert_to_degress(self, value):
|
||||
"""Helper function to convert the GPS coordinates stored in
|
||||
the EXIF to degrees in float format."""
|
||||
d0 = value[0][0]
|
||||
d1 = value[0][1]
|
||||
d = float(d0) / float(d1)
|
||||
|
||||
m0 = value[1][0]
|
||||
m1 = value[1][1]
|
||||
m = float(m0) / float(m1)
|
||||
|
||||
s0 = value[2][0]
|
||||
s1 = value[2][1]
|
||||
s = float(s0) / float(s1)
|
||||
|
||||
return d + (m / 60.0) + (s / 3600.0)
|
||||
|
||||
def get_lat_lon(self):
|
||||
"""Returns the latitude and longitude, if available, from the
|
||||
provided exif_data (obtained through get_exif_data above)."""
|
||||
lat = None
|
||||
lon = None
|
||||
|
||||
gps_info = self.get_gps_info()
|
||||
if gps_info is None:
|
||||
return None
|
||||
|
||||
gps_latitude = self._get_if_exist(gps_info, "GPSLatitude")
|
||||
gps_latitude_ref = self._get_if_exist(gps_info, 'GPSLatitudeRef')
|
||||
gps_longitude = self._get_if_exist(gps_info, 'GPSLongitude')
|
||||
gps_longitude_ref = self._get_if_exist(gps_info, 'GPSLongitudeRef')
|
||||
|
||||
if (gps_latitude and gps_latitude_ref
|
||||
and gps_longitude and gps_longitude_ref):
|
||||
lat = self._convert_to_degress(gps_latitude)
|
||||
if gps_latitude_ref != "N":
|
||||
lat = 0 - lat
|
||||
|
||||
lon = self._convert_to_degress(gps_longitude)
|
||||
if gps_longitude_ref != "E":
|
||||
lon = 0 - lon
|
||||
|
||||
if isinstance(lat, float) and isinstance(lon, float):
|
||||
return lat, lon
|
||||
else:
|
||||
return None
|
||||
|
||||
def calc_tuple(self, tup):
|
||||
if tup is None or len(tup) != 2 or tup[1] == 0:
|
||||
return None
|
||||
return int(tup[0]) / int(tup[1])
|
||||
|
||||
def get_gps_info(self):
|
||||
if self._exif is None or not "GPSInfo" in self._exif:
|
||||
return None
|
||||
else:
|
||||
return self._exif["GPSInfo"]
|
||||
|
||||
def get_rotation(self):
|
||||
"""Returns the direction of the GPS receiver in degrees."""
|
||||
gps_info = self.get_gps_info()
|
||||
if gps_info is None:
|
||||
return None
|
||||
|
||||
for tag in ('GPSImgDirection', 'GPSTrack'):
|
||||
gps_direction = self._get_if_exist(gps_info, tag)
|
||||
direction = self.calc_tuple(gps_direction)
|
||||
if direction == None:
|
||||
continue
|
||||
else:
|
||||
return direction
|
||||
return None
|
||||
|
||||
def get_speed(self):
|
||||
"""Returns the GPS speed in km/h or None if it does not exists."""
|
||||
gps_info = self.get_gps_info()
|
||||
if gps_info is None:
|
||||
return None
|
||||
|
||||
if not "GPSSpeed" in gps_info or not "GPSSpeedRef" in gps_info:
|
||||
return None
|
||||
speed_frac = gps_info["GPSSpeed"]
|
||||
speed_ref = gps_info["GPSSpeedRef"]
|
||||
|
||||
speed = self.calc_tuple(speed_frac)
|
||||
if speed is None or speed_ref is None:
|
||||
return None
|
||||
|
||||
speed_ref = speed_ref.lower()
|
||||
if speed_ref == "k":
|
||||
pass # km/h - we are happy.
|
||||
elif speed_ref == "m":
|
||||
#Miles pr. hour => km/h
|
||||
speed *= 1.609344
|
||||
elif speed_ref == "n":
|
||||
# Knots => km/h
|
||||
speed *= 1.852
|
||||
else:
|
||||
print "Warning: Unknown format for GPS speed '%s' in '%s'." % (
|
||||
speed_ref, self._filepath)
|
||||
print "Please file a bug and attache the image."
|
||||
return None
|
||||
return speed
|
||||
|
||||
def is_ok_num(self, val, minVal, maxVal):
|
||||
try:
|
||||
num = int(val)
|
||||
except ValueError:
|
||||
return False
|
||||
if num < minVal or num > maxVal:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_time(self):
|
||||
# Example data
|
||||
# GPSTimeStamp': ((9, 1), (14, 1), (9000, 1000))
|
||||
# 'GPSDateStamp': u'2015:05:17'
|
||||
gps_info = self.get_gps_info()
|
||||
if gps_info is None:
|
||||
return None
|
||||
|
||||
if not 'GPSTimeStamp' in gps_info or not 'GPSDateStamp' in gps_info:
|
||||
return None
|
||||
timestamp = gps_info['GPSTimeStamp']
|
||||
datestamp = gps_info['GPSDateStamp']
|
||||
|
||||
if len(timestamp) != 3:
|
||||
raise ExifException("Timestamp does not have length 3: %s" %
|
||||
len(timestamp))
|
||||
(timeH, timeM, timeS) = timestamp
|
||||
h = self.calc_tuple(timeH)
|
||||
m = self.calc_tuple(timeM)
|
||||
s = self.calc_tuple(timeS)
|
||||
if None in (h, m, s):
|
||||
raise ExifException(
|
||||
"Hour, minute or second is not valid: '%s':'%s':'%s'." %
|
||||
(timeH, timeM, timeS))
|
||||
|
||||
if datestamp.count(':') != 2:
|
||||
raise ExifException("Datestamp does not contain 2 colons: '%s'" %
|
||||
datestamp)
|
||||
(y, mon, d) = [int(str) for str in datestamp.split(':')]
|
||||
if not self.is_ok_num(y, 1970, 2100) or not self.is_ok_num(
|
||||
mon, 1, 12) or not self.is_ok_num(d, 1, 31):
|
||||
raise ExifException(
|
||||
"Date parsed from the following is not OK: '%s'" % datestamp)
|
||||
|
||||
return datetime.datetime(y, mon, d, h, m, s)
|
Loading…
Add table
Add a link
Reference in a new issue