gh-58 Fix all pep8 issues reported in non-test files

This commit is contained in:
Jaisen Mathai 2016-01-01 23:23:06 -08:00
parent 3501f2c954
commit 60c4acc1b3
9 changed files with 362 additions and 183 deletions

View File

@ -1,3 +1,3 @@
#!/usr/bin/env bash
nosetests -w elodie/tests
nosetests -w elodie/tests && pep8 elodie --exclude=tests

View File

@ -1,15 +1,17 @@
"""
"""
import sys, getopt
import getopt
import sys
from re import sub
def parse(argv, options, long_options, usage):
try:
opts, args = getopt.getopt(argv, options, long_options)
except getopt.GetoptError:
print usage
sys.exit(2)
return_arguments = {}
for opt, arg in opts:
if opt == '-h':

View File

@ -1,6 +1,6 @@
"""
Author: Jaisen Mathai <jaisen@jmathai.com>
Video package that handles all video operations
General file system methods
"""
import os
import re
@ -11,14 +11,13 @@ from elodie import geolocation
from elodie import constants
from elodie.localstorage import Db
"""
General file system methods
"""
class FileSystem:
"""
Create a directory if it does not already exist..
@param, directory_name, string, A fully qualified path of the directory to create.
@param, directory_name, string, A fully qualified path of the
directory to create.
"""
def create_directory(self, directory_path):
try:
@ -35,10 +34,11 @@ class FileSystem:
"""
Delete a directory only if it's empty.
Instead of checking first using `len([name for name in os.listdir(directory_path)]) == 0`
we catch the OSError exception.
Instead of checking first using `len([name for name in
os.listdir(directory_path)]) == 0` we catch the OSError exception.
@param, directory_name, string, A fully qualified path of the directory to delete.
@param, directory_name, string, A fully qualified path of the directory
to delete.
"""
def delete_directory_if_empty(self, directory_path):
try:
@ -60,7 +60,10 @@ class FileSystem:
for dirname, dirnames, filenames in os.walk(path):
# print path to all filenames.
for filename in filenames:
if(extensions == None or filename.lower().endswith(extensions)):
if(
extensions is None or
filename.lower().endswith(extensions)
):
files.append('%s/%s' % (dirname, filename))
return files
@ -75,7 +78,8 @@ class FileSystem:
"""
Generate file name for a photo or video using its metadata.
We use an ISO8601-like format for the file name prefix.
Instead of colons as the separator for hours, minutes and seconds we use a hyphen.
Instead of colons as the separator for hours, minutes and seconds we use a
hyphen.
https://en.wikipedia.org/wiki/ISO_8601#General_principles
@param, media, Photo|Video, A Photo or Video instance
@ -86,21 +90,39 @@ class FileSystem:
return None
metadata = media.get_metadata()
if(metadata == None):
if(metadata is None):
return None
# If the file has EXIF title we use that in the file name (i.e. my-favorite-photo-img_1234.jpg)
# If the file has EXIF title we use that in the file name
# (i.e. my-favorite-photo-img_1234.jpg)
# We want to remove the date prefix we add to the name.
# This helps when re-running the program on file which were already processed.
base_name = re.sub('^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-', '', metadata['base_name'])
# This helps when re-running the program on file which were already
# processed.
base_name = re.sub(
'^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}-',
'',
metadata['base_name']
)
if(len(base_name) == 0):
base_name = metadata['base_name']
if('title' in metadata and metadata['title'] is not None and len(metadata['title']) > 0):
if(
'title' in metadata and
metadata['title'] is not None and
len(metadata['title']) > 0
):
title_sanitized = re.sub('\W+', '-', metadata['title'].strip())
base_name = base_name.replace('-%s' % title_sanitized, '')
base_name = '%s-%s' % (base_name, title_sanitized)
file_name = '%s-%s.%s' % (time.strftime('%Y-%m-%d_%H-%M-%S', metadata['date_taken']), base_name, metadata['extension'])
file_name = '%s-%s.%s' % (
time.strftime(
'%Y-%m-%d_%H-%M-%S',
metadata['date_taken']
),
base_name,
metadata['extension']
)
return file_name.lower()
"""
@ -125,8 +147,14 @@ class FileSystem:
if(metadata['album'] is not None):
path.append(metadata['album'])
elif(metadata['latitude'] is not None and metadata['longitude'] is not None):
place_name = geolocation.place_name(metadata['latitude'], metadata['longitude'])
elif(
metadata['latitude'] is not None and
metadata['longitude'] is not None
):
place_name = geolocation.place_name(
metadata['latitude'],
metadata['longitude']
)
if(place_name is not None):
path.append(place_name)
@ -134,7 +162,7 @@ class FileSystem:
if(len(path) < 2):
path.append('Unknown Location')
#return '/'.join(path[::-1])
# return '/'.join(path[::-1])
return '/'.join(path)
def process_file(self, _file, destination, media, **kwargs):
@ -156,20 +184,24 @@ class FileSystem:
db = Db()
checksum = db.checksum(_file)
if(checksum == None):
if(constants.debug == True):
if(checksum is None):
if(constants.debug is True):
print 'Could not get checksum for %s. Skipping...' % _file
return
# If duplicates are not allowed and this hash exists in the db then we return
if(allowDuplicate == False and db.check_hash(checksum) == True):
if(constants.debug == True):
print '%s already exists at %s. Skipping...' % (_file, db.get_hash(checksum))
# If duplicates are not allowed and this hash exists in the db then we
# return
if(allowDuplicate is False and db.check_hash(checksum) is True):
if(constants.debug is True):
print '%s already exists at %s. Skipping...' % (
_file,
db.get_hash(checksum)
)
return
self.create_directory(dest_directory)
if(move == True):
if(move is True):
stat = os.stat(_file)
shutil.move(_file, dest_path)
os.utime(dest_path, (stat.st_atime, stat.st_mtime))
@ -191,22 +223,34 @@ class FileSystem:
video_file_path = video.get_file_path()
# Initialize date taken to what's returned from the metadata function.
# If the folder and file name follow a time format of YYYY-MM/DD-IMG_0001.JPG then we override the date_taken
# If the folder and file name follow a time format of
# YYYY-MM/DD-IMG_0001.JPG then we override the date_taken
(year, month, day) = [None] * 3
directory = os.path.dirname(video_file_path)
# If the directory matches we get back a match with groups() = (year, month)
# If the directory matches we get back a match with
# groups() = (year, month)
year_month_match = re.search('(\d{4})-(\d{2})', directory)
if(year_month_match is not None):
(year, month) = year_month_match.groups()
day_match = re.search('^(\d{2})', os.path.basename(video.get_file_path()))
day_match = re.search(
'^(\d{2})',
os.path.basename(video.get_file_path())
)
if(day_match is not None):
day = day_match.group(1)
# check if the file system path indicated a date and if so we override the metadata value
# check if the file system path indicated a date and if so we
# override the metadata value
if(year is not None and month is not None):
if(day is not None):
date_taken = time.strptime('{}-{}-{}'.format(year, month, day), '%Y-%m-%d')
date_taken = time.strptime(
'{}-{}-{}'.format(year, month, day),
'%Y-%m-%d'
)
else:
date_taken = time.strptime('{}-{}'.format(year, month), '%Y-%m')
date_taken = time.strptime(
'{}-{}'.format(year, month),
'%Y-%m'
)
os.utime(video_file_path, (time.time(), time.mktime(date_taken)))

View File

@ -11,6 +11,7 @@ import urllib
from elodie import constants
from elodie.localstorage import Db
class Fraction(fractions.Fraction):
"""Only create Fractions from floats.
>>> Fraction(0.3)
@ -22,6 +23,7 @@ class Fraction(fractions.Fraction):
"""Should be compatible with Python 2.6, though untested."""
return fractions.Fraction.from_float(value).limit_denominator(99999)
def coordinates_by_name(name):
# Try to get cached location first
db = Db()
@ -36,57 +38,78 @@ def coordinates_by_name(name):
geolocation_info = lookup(name)
if(geolocation_info is not None):
if('results' in geolocation_info and len(geolocation_info['results']) != 0 and
'locations' in geolocation_info['results'][0] and len(geolocation_info['results'][0]['locations']) != 0):
if(
'results' in geolocation_info and
len(geolocation_info['results']) != 0 and
'locations' in geolocation_info['results'][0] and
len(geolocation_info['results'][0]['locations']) != 0
):
# By default we use the first entry unless we find one with geocodeQuality=city.
use_location = geolocation_info['results'][0]['locations'][0]['latLng']
# Loop over the locations to see if we come accross a geocodeQuality=city.
# By default we use the first entry unless we find one with
# geocodeQuality=city.
geolocation_result = geolocation_info['results'][0]
use_location = geolocation_result['locations'][0]['latLng']
# Loop over the locations to see if we come accross a
# geocodeQuality=city.
# If we find a city we set that to the use_location and break
for location in geolocation_info['results'][0]['locations']:
if('latLng' in location and 'lat' in location['latLng'] and 'lng' in location['latLng'] and location['geocodeQuality'].lower() == 'city'):
for location in geolocation_result['locations']:
if(
'latLng' in location and
'lat' in location['latLng'] and
'lng' in location['latLng'] and
location['geocodeQuality'].lower() == 'city'
):
use_location = location['latLng']
break
return {
'latitude': use_location['lat'],
'longitude': use_location['lng']
}
return None
def decimal_to_dms(decimal, signed=True):
# if decimal is negative we need to make the degrees and minutes negative also
# if decimal is negative we need to make the degrees and minutes
# negative also
sign = 1
if(decimal < 0):
sign = -1
# http://anothergisblog.blogspot.com/2011/11/convert-decimal-degree-to-degrees.html
# http://anothergisblog.blogspot.com/2011/11/convert-decimal-degree-to-degrees.html # noqa
degrees = int(decimal)
subminutes = abs((decimal - int(decimal)) * 60)
minutes = int(subminutes) * sign
subseconds = abs((subminutes - int(subminutes)) * 60) * sign
subseconds_fraction = Fraction(subseconds)
if(signed == False):
if(signed is False):
degrees = abs(degrees)
minutes = abs(minutes)
subseconds_fraction = Fraction(abs(subseconds))
return (pyexiv2.Rational(degrees, 1), pyexiv2.Rational(minutes, 1), pyexiv2.Rational(subseconds_fraction.numerator, subseconds_fraction.denominator))
def dms_to_decimal(degrees, minutes, seconds, sign=' '):
return (-1 if sign[0] in 'SWsw' else 1) * (
float(degrees) +
float(minutes) / 60 +
float(seconds) / 3600
return (
pyexiv2.Rational(degrees, 1),
pyexiv2.Rational(minutes, 1),
pyexiv2.Rational(subseconds_fraction.numerator, subseconds_fraction.denominator) # noqa
)
def dms_to_decimal(degrees, minutes, seconds, direction=' '):
sign = 1
if(direction[0] in 'NEne'):
sign = -1
return (
float(degrees) + float(minutes) / 60 + float(seconds) / 3600
) * sign
def get_key():
config_file = '%s/config.ini' % constants.application_directory
if not path.exists(config_file):
return None
config = ConfigParser()
config.read(config_file)
if('MapQuest' not in config.sections()):
@ -94,16 +117,17 @@ def get_key():
return config.get('MapQuest', 'key')
def place_name(lat, lon):
# Try to get cached location first
db = Db()
# 3km distace radious for a match
cached_place_name = db.get_location_name(lat, lon,3000)
cached_place_name = db.get_location_name(lat, lon, 3000)
if(cached_place_name is not None):
return cached_place_name
lookup_place_name = None;
lookup_place_name = None
geolocation_info = reverse_lookup(lat, lon)
if(geolocation_info is not None):
if('address' in geolocation_info):
@ -130,18 +154,22 @@ def reverse_lookup(lat, lon):
try:
params = {'format': 'json', 'key': key, 'lat': lat, 'lon': lon}
r = requests.get('http://open.mapquestapi.com/nominatim/v1/reverse.php?%s' % urllib.urlencode(params))
r = requests.get(
'http://open.mapquestapi.com/nominatim/v1/reverse.php?%s' %
urllib.urlencode(params)
)
return r.json()
except requests.exceptions.RequestException as e:
if(constants.debug == True):
if(constants.debug is True):
print e
return None
except ValueError as e:
if(constants.debug == True):
if(constants.debug is True):
print r.text
print e
return None
def lookup(name):
if(name is None or len(name) == 0):
return None
@ -150,16 +178,19 @@ def lookup(name):
try:
params = {'format': 'json', 'key': key, 'location': name}
if(constants.debug == True):
print 'http://open.mapquestapi.com/geocoding/v1/address?%s' % urllib.urlencode(params)
r = requests.get('http://open.mapquestapi.com/geocoding/v1/address?%s' % urllib.urlencode(params))
if(constants.debug is True):
print 'http://open.mapquestapi.com/geocoding/v1/address?%s' % urllib.urlencode(params) # noqa
r = requests.get(
'http://open.mapquestapi.com/geocoding/v1/address?%s' %
urllib.urlencode(params)
)
return r.json()
except requests.exceptions.RequestException as e:
if(constants.debug == True):
if(constants.debug is True):
print e
return None
except ValueError as e:
if(constants.debug == True):
if(constants.debug is True):
print r.text
print e
return None

View File

@ -6,9 +6,11 @@ import sys
from elodie import constants
class Db(object):
def __init__(self):
# verify that the application directory (~/.elodie) exists, else create it
# verify that the application directory (~/.elodie) exists,
# else create it
if not os.path.exists(constants.application_directory):
os.makedirs(constants.application_directory)
@ -20,7 +22,8 @@ class Db(object):
self.hash_db = {}
# We know from above that this file exists so we open it for reading only.
# We know from above that this file exists so we open it
# for reading only.
with open(constants.hash_db, 'r') as f:
try:
self.hash_db = json.load(f)
@ -35,7 +38,8 @@ class Db(object):
self.location_db = []
# We know from above that this file exists so we open it for reading only.
# We know from above that this file exists so we open it
# for reading only.
with open(constants.location_db, 'r') as f:
try:
self.location_db = json.load(f)
@ -44,14 +48,14 @@ class Db(object):
def add_hash(self, key, value, write=False):
self.hash_db[key] = value
if(write == True):
if(write is True):
self.update_hash_db()
def check_hash(self, key):
return key in self.hash_db
def get_hash(self, key):
if(self.check_hash(key) == True):
if(self.check_hash(key) is True):
return self.hash_db[key]
return None
@ -88,27 +92,29 @@ class Db(object):
data['long'] = longitude
data['name'] = place
self.location_db.append(data)
if(write == True):
if(write is True):
self.update_location_db()
def get_location_name(self, latitude, longitude,threshold_m):
def get_location_name(self, latitude, longitude, threshold_m):
last_d = sys.maxint
name = None
for data in self.location_db:
# As threshold is quite smal use simple math
# From http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points
# From http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points # noqa
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(radians, [longitude, latitude, data['long'], data['lat']])
lon1, lat1, lon2, lat2 = map(
radians,
[longitude, latitude, data['long'], data['lat']]
)
R = 6371000 # radius of the earth in m
x = (lon2 - lon1) * cos( 0.5*(lat2+lat1) )
x = (lon2 - lon1) * cos(0.5*(lat2+lat1))
y = lat2 - lat1
d = R * sqrt( x*x + y*y )
d = R * sqrt(x*x + y*y)
# Use if closer then threshold_km reuse lookup
if(d <= threshold_m and d < last_d):
#print "Found in cached location dist: %d m" % d
name = data['name'];
name = data['name']
last_d = d
return name

View File

@ -1,6 +1,6 @@
"""
Author: Jaisen Mathai <jaisen@jmathai.com>
Media package that handles all video operations
Media package that's a parent class for media objects
"""
# load modules
@ -17,9 +17,7 @@ import re
import subprocess
import time
"""
Media class for general video operations
"""
class Media(object):
# class / static variable accessible through get_valid_extensions()
__name__ = 'Media'
@ -30,7 +28,7 @@ class Media(object):
def __init__(self, source=None):
self.source = source
self.exif_map = {
'date_taken': ['Exif.Photo.DateTimeOriginal', 'Exif.Image.DateTime'], #, 'EXIF FileDateTime'],
'date_taken': ['Exif.Photo.DateTimeOriginal', 'Exif.Image.DateTime'], # , 'EXIF FileDateTime'], # noqa
'latitude': 'Exif.GPSInfo.GPSLatitude',
'latitude_ref': 'Exif.GPSInfo.GPSLatitudeRef',
'longitude': 'Exif.GPSInfo.GPSLongitude',
@ -51,7 +49,7 @@ class Media(object):
exiftool_attributes = self.get_exiftool_attributes()
if(exiftool_attributes is None or 'album' not in exiftool_attributes):
return None
return exiftool_attributes['album']
"""
@ -65,12 +63,11 @@ class Media(object):
# If exiftool wasn't found we try to brute force the homebrew location
if(exiftool is None):
exiftool = '/usr/local/bin/exiftool'
if(not os.path.isfile(exiftool) or not os.access(exiftool, os.X_OK)):
if(not os.path.isfile(exiftool) or not os.access(exiftool, os.X_OK)): # noqa
return None
return exiftool
"""
Get the full path to the video.
@ -88,14 +85,15 @@ class Media(object):
"""
Read EXIF from a photo file.
We store the result in a member variable so we can call get_exif() often without performance degredation
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
@ -114,7 +112,11 @@ class Media(object):
return False
source = self.source
process_output = subprocess.Popen(['%s "%s"' % (exiftool, source)], stdout=subprocess.PIPE, shell=True)
process_output = subprocess.Popen(
['%s "%s"' % (exiftool, source)],
stdout=subprocess.PIPE,
shell=True
)
output = process_output.stdout.read()
# Get album from exiftool output
@ -131,7 +133,7 @@ class Media(object):
title_return = title_regex.group(1).strip()
if(len(title_return) > 0):
title = title_return
break;
break
self.exiftool_attributes = {
'album': album,
@ -140,7 +142,6 @@ class Media(object):
return self.exiftool_attributes
"""
Get the file extension as a lowercased string.
@ -163,7 +164,7 @@ class Media(object):
if(not self.is_valid()):
return None
if(self.metadata is not None and update_cache == False):
if(self.metadata is not None and update_cache is False):
return self.metadata
source = self.source
@ -181,7 +182,7 @@ class Media(object):
}
return self.metadata
"""
Get the mimetype of the file.
@ -193,11 +194,11 @@ class Media(object):
source = self.source
mimetype = mimetypes.guess_type(source)
if(mimetype == None):
if(mimetype is None):
return None
return mimetype[0]
"""
Get the title for a photo of video
@ -232,9 +233,14 @@ class Media(object):
source = self.source
stat = os.stat(source)
exiftool_config = constants.exiftool_config
if(constants.debug == True):
print '%s -config "%s" -xmp-elodie:Album="%s" "%s"' % (exiftool, exiftool_config, name, source)
process_output = subprocess.Popen(['%s -config "%s" -xmp-elodie:Album="%s" "%s"' % (exiftool, exiftool_config, name, source)], stdout=subprocess.PIPE, shell=True)
if(constants.debug is True):
print '%s -config "%s" -xmp-elodie:Album="%s" "%s"' % (exiftool, exiftool_config, name, source) # noqa
process_output = subprocess.Popen(
['%s -config "%s" -xmp-elodie:Album="%s" "%s"' %
(exiftool, exiftool_config, name, source)],
stdout=subprocess.PIPE,
shell=True
)
streamdata = process_output.communicate()[0]
if(process_output.returncode != 0):
@ -266,14 +272,16 @@ class Media(object):
self.set_album(folder)
return True
"""
Specifically update the basename attribute in the metadata dictionary for this instance.
Specifically update the basename attribute in the metadata
dictionary for this instance.
This is used for when we update the EXIF title of a media file.
Since that determines the name of a file if we update the title of a file more than once it appends to the file name.
Since that determines the name of a file if we update the
title of a file more than once it appends to the file name.
I.e. 2015-12-31_00-00-00-my-first-title-my-second-title.jpg
@param, string, new_basename, New basename of file (with the old title removed
@param, string, new_basename, New basename of file
(with the old title removed)
"""
def set_metadata_basename(self, new_basename):
self.get_metadata()

View File

@ -20,9 +20,7 @@ from elodie import constants
from media import Media
from elodie import geolocation
"""
Photo class for general photo operations
"""
class Photo(Media):
__name__ = 'Photo'
extensions = ('jpg', 'jpeg', 'nef', 'dng', 'gif')
@ -35,7 +33,7 @@ class Photo(Media):
# We only want to parse EXIF once so we store it here
self.exif = None
"""
Get the duration of a photo in seconds.
Uses ffmpeg/ffprobe
@ -47,11 +45,17 @@ class Photo(Media):
return None
source = self.source
result = subprocess.Popen(['ffprobe', source],
stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
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 re.search(
'(\d{2}:\d{2}.\d{2})',
key
).group(1).replace('.', ':')
return None
"""
@ -63,26 +67,40 @@ class Photo(Media):
if(not self.is_valid()):
return None
key = self.exif_map['longitude'] if type == 'longitude' else self.exif_map['latitude']
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
# 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'):
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'):
if(type == 'longitude' and str(exif[self.exif_map['longitude_ref']].value) == 'W'): # noqa
londir = -1
coords = exif[key].value
if(type == 'latitude'):
return float(str(LatLon.Latitude(degree=coords[0], minute=coords[1], second=coords[2]))) * latdir
lat_val = LatLon.Latitude(
degree=coords[0],
minute=coords[1],
second=coords[2]
)
return float(str(lat_val)) * latdir
else:
return float(str(LatLon.Longitude(degree=coords[0], minute=coords[1], second=coords[2]))) * londir
lon_val = LatLon.Longitude(
degree=coords[0],
minute=coords[1],
second=coords[2]
)
return float(str(lon_val)) * londir
except KeyError:
return None
@ -97,21 +115,23 @@ class Photo(Media):
return None
source = self.source
seconds_since_epoch = min(os.path.getmtime(source), os.path.getctime(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 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
# 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):
seconds_since_epoch = time.mktime(exif[key].value.timetuple())
break;
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 == True):
if(constants.debug is True):
print e
pass
@ -121,8 +141,9 @@ class Photo(Media):
return time.gmtime(seconds_since_epoch)
"""
Check the file extension against valid file extensions as returned by self.extensions
Check the file extension against valid file extensions as returned
by self.extensions
@returns, boolean
"""
def is_valid(self):
@ -131,7 +152,7 @@ class Photo(Media):
# 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 False
return os.path.splitext(source)[1][1:].lower() in self.extensions
@ -172,10 +193,10 @@ class Photo(Media):
exif_metadata = pyexiv2.ImageMetadata(source)
exif_metadata.read()
exif_metadata['Exif.GPSInfo.GPSLatitude'] = geolocation.decimal_to_dms(latitude, False)
exif_metadata['Exif.GPSInfo.GPSLatitudeRef'] = pyexiv2.ExifTag('Exif.GPSInfo.GPSLatitudeRef', 'N' if latitude >= 0 else 'S')
exif_metadata['Exif.GPSInfo.GPSLongitude'] = geolocation.decimal_to_dms(longitude, False)
exif_metadata['Exif.GPSInfo.GPSLongitudeRef'] = pyexiv2.ExifTag('Exif.GPSInfo.GPSLongitudeRef', 'E' if longitude >= 0 else 'W')
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

View File

@ -20,12 +20,10 @@ from elodie import constants
from elodie import plist_parser
from media import Media
"""
Video class for general video operations
"""
class Video(Media):
__name__ = 'Video'
extensions = ('avi','m4v','mov','mp4','3gp')
extensions = ('avi', 'm4v', 'mov', 'mp4', '3gp')
"""
@param, source, string, The fully qualified path to the video file
@ -43,12 +41,11 @@ class Video(Media):
avmetareadwrite = find_executable('avmetareadwrite')
if(avmetareadwrite is None):
avmetareadwrite = '/usr/bin/avmetareadwrite'
if(not os.path.isfile(avmetareadwrite) or not os.access(avmetareadwrite, os.X_OK)):
if(not os.path.isfile(avmetareadwrite) or not os.access(avmetareadwrite, os.X_OK)): # noqa
return None
return avmetareadwrite
"""
Get latitude or longitude of photo from EXIF
@ -71,7 +68,7 @@ class Video(Media):
direction = direction.group(0)
decimal_degrees = float(coordinate[0]) + float(coordinate[1])/60 + float(coordinate[2])/3600
decimal_degrees = float(coordinate[0]) + float(coordinate[1])/60 + float(coordinate[2])/3600 # noqa
if(direction == 'S' or direction == 'W'):
decimal_degrees = decimal_degrees * -1
@ -89,9 +86,10 @@ class Video(Media):
source = self.source
# We need to parse a string from EXIF into a timestamp.
# We use date.strptime -> .timetuple -> time.mktime to do the conversion in the local timezone
# We use date.strptime -> .timetuple -> time.mktime to do the
# conversion in the local timezone
# If the time is not found in EXIF we update EXIF
seconds_since_epoch = min(os.path.getmtime(source), os.path.getctime(source))
seconds_since_epoch = min(os.path.getmtime(source), os.path.getctime(source)) # noqa
time_found_in_exif = False
exif_data = self.get_exif()
for key in ['Creation Date', 'Media Create Date']:
@ -99,7 +97,12 @@ class Video(Media):
if(date is not None):
date_string = date.group(1)
try:
exif_seconds_since_epoch = time.mktime(datetime.strptime(date_string, '%Y:%m:%d %H:%M:%S').timetuple())
exif_seconds_since_epoch = time.mktime(
datetime.strptime(
date_string,
'%Y:%m:%d %H:%M:%S'
).timetuple()
)
if(exif_seconds_since_epoch < seconds_since_epoch):
seconds_since_epoch = exif_seconds_since_epoch
time_found_in_exif = True
@ -111,7 +114,7 @@ class Video(Media):
return None
return time.gmtime(seconds_since_epoch)
"""
Get the duration of a video in seconds.
Uses ffmpeg/ffprobe
@ -123,16 +126,23 @@ class Video(Media):
return None
source = self.source
result = subprocess.Popen(['ffprobe', source],
stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
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 re.search(
'(\d{2}:\d{2}.\d{2})',
key
).group(1).replace('.', ':')
return None
"""
Get exif data from video file.
Not all video files have exif and this currently relies on the CLI exiftool program
Not all video files have exif and this currently relies on
the CLI exiftool program
@returns, string or None if exiftool is not found
"""
@ -142,12 +152,17 @@ class Video(Media):
return None
source = self.source
process_output = subprocess.Popen(['%s "%s"' % (exiftool, source)], stdout=subprocess.PIPE, shell=True)
process_output = subprocess.Popen(
['%s "%s"' % (exiftool, source)],
stdout=subprocess.PIPE,
shell=True
)
return process_output.stdout.read()
"""
Check the file extension against valid file extensions as returned by self.extensions
Check the file extension against valid file extensions as
returned by self.extensions
@returns, boolean
"""
def is_valid(self):
@ -168,8 +183,14 @@ class Video(Media):
source = self.source
result = self.__update_using_plist(time=date_taken_as_datetime)
if(result == True):
os.utime(source, (int(time.time()), time.mktime(date_taken_as_datetime.timetuple())))
if(result is True):
os.utime(
source,
(
int(time.time()),
time.mktime(date_taken_as_datetime.timetuple())
)
)
return result
@ -185,7 +206,7 @@ class Video(Media):
if(latitude is None or longitude is None):
return False
result = self.__update_using_plist(latitude=latitude, longitude=longitude)
result = self.__update_using_plist(latitude=latitude, longitude=longitude) # noqa
return result
"""
@ -210,9 +231,11 @@ class Video(Media):
1) Check if avmetareadwrite is installed
2) Export a plist file to a temporary location from the source file
3) Regex replace values in the plist file
4) Update the source file using the updated plist and save it to a temporary location
4) Update the source file using the updated plist and save it to a
temporary location
5) Validate that the metadata in the updated temorary movie is valid
6) Copystat permission and time bits from the source file to the temporary movie
6) Copystat permission and time bits from the source file to the
temporary movie
7) Move the temporary file to overwrite the source file
@param, latitude, float, Latitude of the file
@ -221,33 +244,49 @@ class Video(Media):
@returns, boolean
"""
def __update_using_plist(self, **kwargs):
if('latitude' not in kwargs and 'longitude' not in kwargs and 'time' not in kwargs and 'title' not in kwargs):
if(constants.debug == True):
if(
'latitude' not in kwargs and
'longitude' not in kwargs and
'time' not in kwargs and
'title' not in kwargs
):
if(constants.debug is True):
print 'No lat/lon passed into __create_plist'
return False
avmetareadwrite = self.get_avmetareadwrite()
if(avmetareadwrite is None):
if(constants.debug == True):
if(constants.debug is True):
print 'Could not find avmetareadwrite'
return False
source = self.source
# First we need to write the plist for an existing file to a temporary location
# First we need to write the plist for an existing file
# to a temporary location
with tempfile.NamedTemporaryFile() as plist_temp:
# We need to write the plist file in a child process but also block for it to be complete.
# We need to write the plist file in a child process
# but also block for it to be complete.
# http://stackoverflow.com/a/5631819/1318758
avmetareadwrite_generate_plist_command = '%s -p "%s" "%s"' % (avmetareadwrite, plist_temp.name, source)
write_process = subprocess.Popen([avmetareadwrite_generate_plist_command], stdout=subprocess.PIPE, shell=True)
avmetareadwrite_generate_plist_command = '%s -p "%s" "%s"' % (
avmetareadwrite,
plist_temp.name,
source
)
write_process = subprocess.Popen(
[avmetareadwrite_generate_plist_command],
stdout=subprocess.PIPE,
shell=True
)
streamdata = write_process.communicate()[0]
if(write_process.returncode != 0):
if(constants.debug == True):
if(constants.debug is True):
print 'Failed to generate plist file'
return False
plist = plist_parser.Plist(plist_temp.name)
# Depending on the kwargs that were passed in we regex the plist_text before we write it back.
# Depending on the kwargs that were passed in we regex
# the plist_text before we write it back.
plist_should_be_written = False
if('latitude' in kwargs and 'longitude' in kwargs):
latitude = str(abs(kwargs['latitude'])).lstrip('0')
@ -258,10 +297,16 @@ class Video(Media):
lat_sign = '+' if latitude > 0 else '-'
# We need to zeropad the longitude.
# No clue why - ask Apple.
# We set the sign to + or - and then we take the absolute value and fill it.
# We set the sign to + or - and then we take the absolute value
# and fill it.
lon_sign = '+' if longitude > 0 else '-'
longitude_str = '{:9.5f}'.format(abs(longitude)).replace(' ', '0')
lat_lon_str = '%s%s%s%s' % (lat_sign, latitude, lon_sign, longitude_str)
longitude_str = '{:9.5f}'.format(abs(longitude)).replace(' ', '0') # noqa
lat_lon_str = '%s%s%s%s' % (
lat_sign,
latitude,
lon_sign,
longitude_str
)
plist.update_key('common/location', lat_lon_str)
plist_should_be_written = True
@ -277,13 +322,12 @@ class Video(Media):
hms = [int(x) for x in time_parts[1].split(':')]
if(hms is not None):
d = datetime(ymd[0], ymd[1], ymd[2], hms[0], hms[1], hms[2])
d = datetime(ymd[0], ymd[1], ymd[2], hms[0], hms[1], hms[2]) # noqa
else:
d = datetime(ymd[0], ymd[1], ymd[2], 12, 00, 00)
offset = time.strftime("%z", time.gmtime(time.time()))
time_string = d.strftime('%Y-%m-%dT%H:%M:%S{}'.format(offset))
#2015-10-09T17:11:30-0700
time_string = d.strftime('%Y-%m-%dT%H:%M:%S{}'.format(offset)) # noqa
plist.update_key('common/creationDate', time_string)
plist_should_be_written = True
@ -296,13 +340,15 @@ class Video(Media):
plist_final = plist_temp.name
plist.write_file(plist_final)
else:
if(constants.debug == True):
if(constants.debug is True):
print 'Nothing to update, plist unchanged'
return False
# We create a temporary file to save the modified file to.
# If the modification is successful we will update the existing file.
# We can't call self.get_metadata else we will run into infinite loops
# If the modification is successful we will update the
# existing file.
# We can't call self.get_metadata else we will run into
# infinite loops
# metadata = self.get_metadata()
temp_movie = None
with tempfile.NamedTemporaryFile() as temp_file:
@ -310,23 +356,44 @@ class Video(Media):
# We need to block until the child process completes.
# http://stackoverflow.com/a/5631819/1318758
avmetareadwrite_command = '%s -a %s "%s" "%s"' % (avmetareadwrite, plist_final, source, temp_movie)
update_process = subprocess.Popen([avmetareadwrite_command], stdout=subprocess.PIPE, shell=True)
avmetareadwrite_command = '%s -a %s "%s" "%s"' % (
avmetareadwrite,
plist_final,
source,
temp_movie
)
update_process = subprocess.Popen(
[avmetareadwrite_command],
stdout=subprocess.PIPE,
shell=True
)
streamdata = update_process.communicate()[0]
if(update_process.returncode != 0):
if(constants.debug == True):
print '%s did not complete successfully' % avmetareadwrite_command
if(constants.debug is True):
print '%s did not complete successfully' % avmetareadwrite_command # noqa
return False
# Before we do anything destructive we confirm that the file is in tact.
# Before we do anything destructive we confirm that the
# file is in tact.
check_media = Video(temp_movie)
check_metadata = check_media.get_metadata()
if(('latitude' in kwargs and 'longitude' in kwargs and check_metadata['latitude'] is None and check_metadata['longitude'] is None) or ('time' in kwargs and check_metadata['date_taken'] is None)):
if(constants.debug == True):
if(
(
'latitude' in kwargs and
'longitude' in kwargs and
check_metadata['latitude'] is None and
check_metadata['longitude'] is None
) or (
'time' in kwargs and
check_metadata['date_taken'] is None
)
):
if(constants.debug is True):
print 'Something went wrong updating video metadata'
return False
# Copy file information from original source to temporary file before copying back over
# Copy file information from original source to temporary file
# before copying back over
shutil.copystat(source, temp_movie)
stat = os.stat(source)
shutil.move(temp_movie, source)
@ -343,6 +410,7 @@ class Video(Media):
def get_valid_extensions(Video):
return Video.extensions
class Transcode(object):
# Constructor takes a video object as it's parameter
def __init__(self, video=None):

View File

@ -2,6 +2,7 @@
Author: Jaisen Mathai <jaisen@jmathai.com>
Parse OS X plists.
Wraps standard lib plistlib (https://docs.python.org/3/library/plistlib.html)
Plist class to parse and interact with a plist file.
"""
# load modules
@ -9,9 +10,7 @@ from os import path
import plistlib
"""
Plist class to parse and interact with a plist file.
"""
class Plist(object):
def __init__(self, source):
if(path.isfile(source) == False):