ordigi/elodie/media/photo.py

228 lines
6.7 KiB
Python
Raw Normal View History

2015-10-07 10:48:01 +02:00
"""
Author: Jaisen Mathai <jaisen@jmathai.com>
Photo package that handles all photo operations
"""
import imghdr
import LatLon
2015-10-07 10:48:01 +02:00
import os
import pyexiv2
2015-10-07 10:48:01 +02:00
import re
import subprocess
2015-10-07 10:48:01 +02:00
import time
from elodie import constants
from media import Media
from elodie import geolocation
2015-10-07 10:48:01 +02:00
2015-10-07 10:48:01 +02:00
class Photo(Media):
__name__ = 'Photo'
extensions = ('jpg', 'jpeg', 'nef', 'dng', 'gif')
2015-10-07 10:48:01 +02:00
"""
@param, source, string, The fully qualified path to the photo file
"""
def __init__(self, source=None):
super(Photo, self).__init__(source)
# We only want to parse EXIF once so we store it here
self.exif = None
2015-10-07 10:48:01 +02:00
"""
Get the duration of a photo in seconds.
Uses ffmpeg/ffprobe
@returns, string or None for a non-photo file
"""
def get_duration(self):
if(not self.is_valid()):
return None
source = self.source
result = subprocess.Popen(
['ffprobe', source],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
2015-10-07 10:48:01 +02:00
for key in result.stdout.readlines():
if 'Duration' in key:
return re.search(
'(\d{2}:\d{2}.\d{2})',
key
).group(1).replace('.', ':')
2015-10-07 10:48:01 +02:00
return None
"""
Get latitude or longitude of photo from EXIF
@returns, float or None if not present in EXIF or a non-photo file
"""
def get_coordinate(self, type='latitude'):
if(not self.is_valid()):
return None
key = self.exif_map['latitude']
if(type == 'longitude'):
key = self.exif_map['longitude']
exif = self.get_exif()
if(key not in exif):
return None
try:
# this is a hack to get the proper direction by negating the
# values for S and W
latdir = 1
if(type == 'latitude' and str(exif[self.exif_map['latitude_ref']].value) == 'S'): # noqa
latdir = -1
londir = 1
if(type == 'longitude' and str(exif[self.exif_map['longitude_ref']].value) == 'W'): # noqa
londir = -1
coords = exif[key].value
if(type == 'latitude'):
lat_val = LatLon.Latitude(
degree=coords[0],
minute=coords[1],
second=coords[2]
)
return float(str(lat_val)) * latdir
else:
lon_val = LatLon.Longitude(
degree=coords[0],
minute=coords[1],
second=coords[2]
)
return float(str(lon_val)) * londir
except KeyError:
return None
"""
Get the date which the photo was taken.
The date value returned is defined by the min() of mtime and ctime.
@returns, time object or None for non-photo files or 0 timestamp
"""
def get_date_taken(self):
if(not self.is_valid()):
return None
source = self.source
seconds_since_epoch = min(os.path.getmtime(source), os.path.getctime(source)) # noqa
# We need to parse a string from EXIF into a timestamp.
# EXIF DateTimeOriginal and EXIF DateTime are both stored
# in %Y:%m:%d %H:%M:%S format
# we use date.strptime -> .timetuple -> time.mktime to do
# the conversion in the local timezone
# EXIF DateTime is already stored as a timestamp
# Sourced from https://github.com/photo/frontend/blob/master/src/libraries/models/Photo.php#L500 # noqa
exif = self.get_exif()
for key in self.exif_map['date_taken']:
try:
if(key in exif):
if(re.match('\d{4}(-|:)\d{2}(-|:)\d{2}', str(exif[key].value)) is not None): # noqa
seconds_since_epoch = time.mktime(exif[key].value.timetuple()) # noqa
break
except BaseException as e:
if(constants.debug is True):
print e
pass
if(seconds_since_epoch == 0):
return None
return time.gmtime(seconds_since_epoch)
"""
Check the file extension against valid file extensions as returned
by self.extensions
@returns, boolean
"""
def is_valid(self):
source = self.source
# gh-4 This checks if the source file is an image.
# It doesn't validate against the list of supported types.
if(imghdr.what(source) is None):
return False
return os.path.splitext(source)[1][1:].lower() in self.extensions
"""
Set the date/time a photo was taken
@param, time, datetime, datetime object of when the photo was taken
@returns, boolean
"""
def set_date_taken(self, time):
if(time is None):
return False
source = self.source
exif_metadata = pyexiv2.ImageMetadata(source)
exif_metadata.read()
exif_metadata['Exif.Photo.DateTimeOriginal'].value = time
exif_metadata['Exif.Image.DateTime'].value = time
exif_metadata.write()
return True
"""
Set lat/lon for a photo
@param, latitude, float, Latitude of the file
@param, longitude, float, Longitude of the file
@returns, boolean
"""
def set_location(self, latitude, longitude):
if(latitude is None or longitude is None):
return False
source = self.source
exif_metadata = pyexiv2.ImageMetadata(source)
exif_metadata.read()
exif_metadata['Exif.GPSInfo.GPSLatitude'] = geolocation.decimal_to_dms(latitude, False) # noqa
exif_metadata['Exif.GPSInfo.GPSLatitudeRef'] = pyexiv2.ExifTag('Exif.GPSInfo.GPSLatitudeRef', 'N' if latitude >= 0 else 'S') # noqa
exif_metadata['Exif.GPSInfo.GPSLongitude'] = geolocation.decimal_to_dms(longitude, False) # noqa
exif_metadata['Exif.GPSInfo.GPSLongitudeRef'] = pyexiv2.ExifTag('Exif.GPSInfo.GPSLongitudeRef', 'E' if longitude >= 0 else 'W') # noqa
exif_metadata.write()
return True
"""
Set title for a photo
@param, latitude, float, Latitude of the file
@param, longitude, float, Longitude of the file
@returns, boolean
"""
def set_title(self, title):
if(title is None):
return False
source = self.source
exif_metadata = pyexiv2.ImageMetadata(source)
exif_metadata.read()
exif_metadata['Xmp.dc.title'] = title
exif_metadata.write()
return True
2015-10-07 10:48:01 +02:00
"""
Static method to access static __valid_extensions variable.
@returns, tuple
"""
@classmethod
def get_valid_extensions(cls):
return cls.extensions