Add initial Photo class

This commit is contained in:
Jaisen Mathai 2015-10-07 01:48:01 -07:00
parent edb21d5e31
commit b7984dff0f
3 changed files with 176 additions and 21 deletions

View File

@ -1,2 +1,3 @@
# elodie
pip install exifread
pip install LatLon

154
elodie/media/photo.py Normal file
View File

@ -0,0 +1,154 @@
"""
Author: Jaisen Mathai <jaisen@jmathai.com>
Photo package that handles all photo operations
"""
# load modules
from sys import argv
from datetime import datetime
import exifread
from fractions import Fraction
import LatLon
import mimetypes
import os
import re
import time
from elodie.media.media import Media
"""
Video class for general photo operations
"""
class Photo(Media):
# class / static variable accessible through get_valid_extensions()
__valid_extensions = ('jpg', 'jpeg')
"""
@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
"""
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))
# 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
exif = self.get_exif()
if('EXIF DateTimeOriginal' in exif):
seconds_since_epoch = time.mktime(datetime.strptime(exif['EXIF DateTimeOriginal'], '%Y:%m:%d %H:%M:%S').timetuple())
elif('EXIF DateTime' in exif):
seconds_since_epoch = time.mktime(datetime.strptime(exif['EXIF DateTime'], '%Y:%m:%d %H:%M:%S').timetuple())
elif('EXIF FileDateTime' in exif):
seconds_since_epoch = exif['EXIF DateTime']
if(seconds_since_epoch == 0):
return None
return time.gmtime(seconds_since_epoch)
"""
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)
for key in result.stdout.readlines():
if 'Duration' in key:
return re.search('(\d{2}:\d{2}.\d{2})', key).group(1).replace('.', ':')
return None
"""
Read EXIF from a photo file.
We store the result in a member variable so we can call get_exif() often without performance degredation
@returns, list or none for a non-photo file
"""
def get_exif(self):
if(not self.is_valid()):
return None
if(self.exif is not None):
return self.exif
source = self.source
with open(source, 'r') as f:
self.exif = exifread.process_file(f, details=False)
return self.exif
"""
Get latitude 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 = 'GPS GPSLongitude' if type == 'longitude' else 'GPS GPSLatitude'
exif = self.get_exif()
if(key not in exif):
return None
coords = [float(Fraction(ratio.num, ratio.den)) for ratio in exif[key].values]
if(key == 'latitude'):
return str(LatLon.Latitude(degree=coords[0], minute=coords[1], second=coords[2]))
else:
return str(LatLon.Longitude(degree=coords[0], minute=coords[1], second=coords[2]))
"""
Get a dictionary of metadata for a photo.
All keys will be present and have a value of None if not obtained.
@returns, dictionary or None for non-photo files
"""
def get_metadata(self):
if(not self.is_valid()):
return None
source = self.source
metadata = {
#"date_taken": self.get_date_taken(),
"latitude": self.get_coordinate('latitude'),
"longitude": self.get_coordinate('longitude'),
"mime_type": self.get_mimetype(),
"base_name": os.path.splitext(os.path.basename(source))[0],
"extension": self.get_extension()
}
return metadata
"""
Static method to access static __valid_extensions variable.
@returns, tuple
"""
@classmethod
def get_valid_extensions(Video):
return Video.__valid_extensions

View File

@ -26,27 +26,6 @@ class Video(Media):
def __init__(self, source=None):
super(Video, self).__init__(source)
"""
Get a dictionary of metadata for a video.
All keys will be present and have a value of None if not obtained.
@returns, dictionary or None for non-video files
"""
def get_metadata(self):
if(not self.is_valid()):
return None
source = self.source
metadata = {
"date_taken": self.get_date_taken(),
"length": self.get_duration(),
"mime_type": self.get_mimetype(),
"base_name": os.path.splitext(os.path.basename(source))[0],
"extension": self.get_extension()
}
return metadata
"""
Get the date which the video was taken.
The date value returned is defined by the min() of mtime and ctime.
@ -82,6 +61,27 @@ class Video(Media):
return re.search('(\d{2}:\d{2}.\d{2})', key).group(1).replace('.', ':')
return None
"""
Get a dictionary of metadata for a video.
All keys will be present and have a value of None if not obtained.
@returns, dictionary or None for non-video files
"""
def get_metadata(self):
if(not self.is_valid()):
return None
source = self.source
metadata = {
"date_taken": self.get_date_taken(),
"length": self.get_duration(),
"mime_type": self.get_mimetype(),
"base_name": os.path.splitext(os.path.basename(source))[0],
"extension": self.get_extension()
}
return metadata
"""
Static method to access static __valid_extensions variable.