Merge pull request #123 from zserg/py3_1

Python 3 compatibility added
This commit is contained in:
Jaisen Mathai 2016-08-22 11:35:59 -07:00 committed by GitHub
commit 09872c82a5
21 changed files with 106 additions and 80 deletions

View File

@ -2,14 +2,15 @@ language: python
dist: trusty dist: trusty
python: python:
- "2.7" - "2.7"
- "3.4"
virtualenv: virtualenv:
system_site_packages: true system_site_packages: true
before_install: before_install:
- "sudo apt-get update -qq" - "sudo apt-get update -qq"
- "sudo apt-get install python-dev python-pip libimage-exiftool-perl -y" - "sudo apt-get install python-dev python-pip libimage-exiftool-perl -y"
install: install:
- "sudo pip install -r elodie/tests/requirements.txt" - "pip install -r elodie/tests/requirements.txt"
- "sudo pip install coveralls" - "pip install coveralls"
# command to run tests # command to run tests
# test mapquest key # test mapquest key
before_script: before_script:

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import print_function
import os import os
import re import re
import sys import sys
@ -35,16 +36,16 @@ def import_file(_file, destination, album_from_folder, trash):
""" """
if not os.path.exists(_file): if not os.path.exists(_file):
if constants.debug: if constants.debug:
print 'Could not find %s' % _file print('Could not find %s' % _file)
print '{"source":"%s", "error_msg":"Could not find %s"}' % \ print('{"source":"%s", "error_msg":"Could not find %s"}' % \
(_file, _file) (_file, _file))
return return
media = Media.get_class_by_file(_file, [Text, Audio, Photo, Video]) media = Media.get_class_by_file(_file, [Text, Audio, Photo, Video])
if not media: if not media:
if constants.debug: if constants.debug:
print 'Not a supported file (%s)' % _file print('Not a supported file (%s)' % _file)
print '{"source":"%s", "error_msg":"Not a supported file"}' % _file print('{"source":"%s", "error_msg":"Not a supported file"}' % _file)
return return
if media.__name__ == 'Video': if media.__name__ == 'Video':
@ -56,7 +57,7 @@ def import_file(_file, destination, album_from_folder, trash):
dest_path = FILESYSTEM.process_file(_file, destination, dest_path = FILESYSTEM.process_file(_file, destination,
media, allowDuplicate=False, move=False) media, allowDuplicate=False, move=False)
if dest_path: if dest_path:
print '%s -> %s' % (_file, dest_path) print('%s -> %s' % (_file, dest_path))
if trash: if trash:
send2trash(_file) send2trash(_file)
@ -109,9 +110,9 @@ def update_location(media, file_path, location_name):
'latitude'], location_coords['longitude']) 'latitude'], location_coords['longitude'])
if not location_status: if not location_status:
if constants.debug: if constants.debug:
print 'Failed to update location' print('Failed to update location')
print ('{"source":"%s",' % file_path, print(('{"source":"%s",' % file_path,
'"error_msg":"Failed to update location"}') '"error_msg":"Failed to update location"}'))
sys.exit(1) sys.exit(1)
return True return True
@ -125,8 +126,8 @@ def update_time(media, file_path, time_string):
elif re.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}\d{2}$', time_string): elif re.match(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}\d{2}$', time_string):
msg = ('Invalid time format. Use YYYY-mm-dd hh:ii:ss or YYYY-mm-dd') msg = ('Invalid time format. Use YYYY-mm-dd hh:ii:ss or YYYY-mm-dd')
if constants.debug: if constants.debug:
print msg print(msg)
print '{"source":"%s", "error_msg":"%s"}' % (file_path, msg) print('{"source":"%s", "error_msg":"%s"}' % (file_path, msg))
sys.exit(1) sys.exit(1)
time = datetime.strptime(time_string, time_format) time = datetime.strptime(time_string, time_format)
@ -150,9 +151,9 @@ def _update(album, location, time, title, files):
for file_path in files: for file_path in files:
if not os.path.exists(file_path): if not os.path.exists(file_path):
if constants.debug: if constants.debug:
print 'Could not find %s' % file_path print('Could not find %s' % file_path)
print '{"source":"%s", "error_msg":"Could not find %s"}' % \ print('{"source":"%s", "error_msg":"Could not find %s"}' % \
(file_path, file_path) (file_path, file_path))
continue continue
file_path = os.path.expanduser(file_path) file_path = os.path.expanduser(file_path)
@ -209,9 +210,9 @@ def _update(album, location, time, title, files):
dest_path = FILESYSTEM.process_file(file_path, destination, dest_path = FILESYSTEM.process_file(file_path, destination,
updated_media, move=True, allowDuplicate=True) updated_media, move=True, allowDuplicate=True)
if constants.debug: if constants.debug:
print u'%s -> %s' % (file_path, dest_path) print(u'%s -> %s' % (file_path, dest_path))
print '{"source":"%s", "destination":"%s"}' % (file_path, print('{"source":"%s", "destination":"%s"}' % (file_path,
dest_path) dest_path))
# If the folder we moved the file out of or its parent are empty # If the folder we moved the file out of or its parent are empty
# we delete it. # we delete it.
FILESYSTEM.delete_directory_if_empty(os.path.dirname(file_path)) FILESYSTEM.delete_directory_if_empty(os.path.dirname(file_path))

View File

@ -1,6 +1,7 @@
""" """
Command line argument parsing for helper scripts. Command line argument parsing for helper scripts.
""" """
from __future__ import print_function
import getopt import getopt
import sys import sys
@ -20,13 +21,13 @@ def parse(argv, options, long_options, usage):
try: try:
opts, args = getopt.getopt(argv, options, long_options) opts, args = getopt.getopt(argv, options, long_options)
except getopt.GetoptError: except getopt.GetoptError:
print usage print(usage)
sys.exit(2) sys.exit(2)
return_arguments = {} return_arguments = {}
for opt, arg in opts: for opt, arg in opts:
if opt == '-h': if opt == '-h':
print usage print(usage)
sys.exit() sys.exit()
else: else:
return_arguments[sub('^-+', '', opt)] = arg return_arguments[sub('^-+', '', opt)] = arg

View File

@ -2,6 +2,7 @@
Helpers for checking for an interacting with external dependencies. These are Helpers for checking for an interacting with external dependencies. These are
things that Elodie requires, but aren't installed automatically for the user. things that Elodie requires, but aren't installed automatically for the user.
""" """
from __future__ import print_function
import os import os
import sys import sys
@ -43,7 +44,7 @@ def verify_dependencies():
""" """
exiftool = get_exiftool() exiftool = get_exiftool()
if exiftool is None: if exiftool is None:
print >>sys.stderr, EXIFTOOL_ERROR print(EXIFTOOL_ERROR, file=sys.stderr)
return False return False
return True return True

View File

@ -273,8 +273,7 @@ class ExifTool(object):
""" """
if not self.running: if not self.running:
raise ValueError("ExifTool instance not running.") raise ValueError("ExifTool instance not running.")
cmd_txt = b"\n".join(params + (b"-execute\n",)) self._process.stdin.write(b"\n".join(params + (b"-execute\n",)))
self._process.stdin.write(cmd_txt.encode("utf-8"))
self._process.stdin.flush() self._process.stdin.flush()
output = b"" output = b""
fd = self._process.stdout.fileno() fd = self._process.stdout.fileno()
@ -403,11 +402,13 @@ class ExifTool(object):
"an iterable of strings") "an iterable of strings")
params = [] params = []
params_utf8 = []
for tag, value in tags.items(): for tag, value in tags.items():
params.append(u'-%s=%s' % (tag, value)) params.append(u'-%s=%s' % (tag, value))
params.extend(filenames) params.extend(filenames)
return self.execute(*params) params_utf8 = [x.encode('utf-8') for x in params]
return self.execute(*params_utf8)
def set_tags(self, tags, filename): def set_tags(self, tags, filename):
"""Writes the values of the specified tags for the given file. """Writes the values of the specified tags for the given file.

View File

@ -3,6 +3,8 @@ General file system methods.
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com> .. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
""" """
from __future__ import print_function
from builtins import object
import os import os
import re import re
@ -176,7 +178,7 @@ class FileSystem(object):
allow_duplicate = kwargs['allowDuplicate'] allow_duplicate = kwargs['allowDuplicate']
if(not media.is_valid()): if(not media.is_valid()):
print '%s is not a valid media file. Skipping...' % _file print('%s is not a valid media file. Skipping...' % _file)
return return
metadata = media.get_metadata() metadata = media.get_metadata()
@ -191,17 +193,17 @@ class FileSystem(object):
checksum = db.checksum(_file) checksum = db.checksum(_file)
if(checksum is None): if(checksum is None):
if(constants.debug is True): if(constants.debug is True):
print 'Could not get checksum for %s. Skipping...' % _file print('Could not get checksum for %s. Skipping...' % _file)
return return
# If duplicates are not allowed and this hash exists in the db then we # If duplicates are not allowed and this hash exists in the db then we
# return # return
if(allow_duplicate is False and db.check_hash(checksum) is True): if(allow_duplicate is False and db.check_hash(checksum) is True):
if(constants.debug is True): if(constants.debug is True):
print '%s already exists at %s. Skipping...' % ( print('%s already exists at %s. Skipping...' % (
_file, _file,
db.get_hash(checksum) db.get_hash(checksum)
) ))
return return
self.create_directory(dest_directory) self.create_directory(dest_directory)

View File

@ -1,32 +1,23 @@
"""Look up geolocation information for media objects.""" """Look up geolocation information for media objects."""
from __future__ import print_function
from __future__ import division
from future import standard_library
from past.utils import old_div
from os import path from os import path
from ConfigParser import ConfigParser from configparser import ConfigParser
import fractions
standard_library.install_aliases() # noqa
import requests import requests
import urllib import urllib.request
import urllib.parse
import urllib.error
from elodie import constants from elodie import constants
from elodie.localstorage import Db from elodie.localstorage import Db
class Fraction(fractions.Fraction):
"""Only create Fractions from floats.
Should be compatible with Python 2.6, though untested.
>>> Fraction(0.3)
Fraction(3, 10)
>>> Fraction(1.1)
Fraction(11, 10)
"""
def __new__(cls, value, ignore=None):
return fractions.Fraction.from_float(value).limit_denominator(99999)
def coordinates_by_name(name): def coordinates_by_name(name):
# Try to get cached location first # Try to get cached location first
db = Db() db = Db()
@ -88,7 +79,8 @@ def dms_to_decimal(degrees, minutes, seconds, direction=' '):
if(direction[0] in 'WSws'): if(direction[0] in 'WSws'):
sign = -1 sign = -1
return ( return (
float(degrees) + float(minutes) / 60 + float(seconds) / 3600 float(degrees) + old_div(float(minutes), 60) +
old_div(float(seconds), 3600)
) * sign ) * sign
@ -159,17 +151,17 @@ def reverse_lookup(lat, lon):
headers = {"Accept-Language": constants.accepted_language} headers = {"Accept-Language": constants.accepted_language}
r = requests.get( r = requests.get(
'http://open.mapquestapi.com/nominatim/v1/reverse.php?%s' % 'http://open.mapquestapi.com/nominatim/v1/reverse.php?%s' %
urllib.urlencode(params), headers=headers urllib.parse.urlencode(params), headers=headers
) )
return r.json() return r.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
if(constants.debug is True): if(constants.debug is True):
print e print(e)
return None return None
except ValueError as e: except ValueError as e:
if(constants.debug is True): if(constants.debug is True):
print r.text print(r.text)
print e print(e)
return None return None
@ -182,18 +174,18 @@ def lookup(name):
try: try:
params = {'format': 'json', 'key': key, 'location': name} params = {'format': 'json', 'key': key, 'location': name}
if(constants.debug is True): if(constants.debug is True):
print 'http://open.mapquestapi.com/geocoding/v1/address?%s' % urllib.urlencode(params) # noqa print('http://open.mapquestapi.com/geocoding/v1/address?%s' % urllib.parse.urlencode(params)) # noqa
r = requests.get( r = requests.get(
'http://open.mapquestapi.com/geocoding/v1/address?%s' % 'http://open.mapquestapi.com/geocoding/v1/address?%s' %
urllib.urlencode(params) urllib.parse.urlencode(params)
) )
return r.json() return r.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
if(constants.debug is True): if(constants.debug is True):
print e print(e)
return None return None
except ValueError as e: except ValueError as e:
if(constants.debug is True): if(constants.debug is True):
print r.text print(r.text)
print e print(e)
return None return None

View File

@ -1,6 +1,8 @@
""" """
Methods for interacting with information Elodie caches about stored media. Methods for interacting with information Elodie caches about stored media.
""" """
from builtins import map
from builtins import object
import hashlib import hashlib
import json import json
@ -141,17 +143,17 @@ class Db(object):
the given latitude and longitude. the given latitude and longitude.
:returns: str, or None if a matching location couldn't be found. :returns: str, or None if a matching location couldn't be found.
""" """
last_d = sys.maxint last_d = sys.maxsize
name = None name = None
for data in self.location_db: for data in self.location_db:
# As threshold is quite small use simple math # As threshold is quite small use simple math
# From http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points # noqa # From http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points # noqa
# convert decimal degrees to radians # convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map( lon1, lat1, lon2, lat2 = list(map(
radians, radians,
[longitude, latitude, data['long'], data['lat']] [longitude, latitude, data['long'], data['lat']]
) ))
r = 6371000 # radius of the earth in m r = 6371000 # radius of the earth in m
x = (lon2 - lon1) * cos(0.5 * (lat2 + lat1)) x = (lon2 - lon1) * cos(0.5 * (lat2 + lat1))

View File

@ -5,8 +5,9 @@ class.
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com> .. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
""" """
from __future__ import absolute_import
from video import Video from .video import Video
class Audio(Video): class Audio(Video):

View File

@ -13,6 +13,11 @@ are used to represent the actual files.
import mimetypes import mimetypes
import os import os
try: # Py3k compatibility
basestring
except NameError:
basestring = (bytes, str)
class Base(object): class Base(object):

View File

@ -8,6 +8,7 @@ are used to represent the actual files.
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com> .. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
""" """
from __future__ import print_function
# load modules # load modules
from elodie import constants from elodie import constants

View File

@ -4,6 +4,8 @@ image objects (JPG, DNG, etc.).
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com> .. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
""" """
from __future__ import print_function
from __future__ import absolute_import
import imghdr import imghdr
import os import os
@ -15,7 +17,7 @@ from re import compile
from elodie import constants from elodie import constants
from media import Media from .media import Media
class Photo(Media): class Photo(Media):
@ -95,7 +97,7 @@ class Photo(Media):
break break
except BaseException as e: except BaseException as e:
if(constants.debug is True): if(constants.debug is True):
print e print(e)
pass pass
if(seconds_since_epoch == 0): if(seconds_since_epoch == 0):

View File

@ -123,7 +123,7 @@ class Text(Base):
self.metadata_line = parsed_json self.metadata_line = parsed_json
except ValueError: except ValueError:
if(constants.debug is True): if(constants.debug is True):
print 'Could not parse JSON from first line: %s' % first_line print('Could not parse JSON from first line: %s' % first_line)
pass pass
def write_metadata(self, **kwargs): def write_metadata(self, **kwargs):

View File

@ -4,6 +4,9 @@ objects (AVI, MOV, etc.).
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com> .. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
""" """
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
# load modules # load modules
from distutils.spawn import find_executable from distutils.spawn import find_executable
@ -13,7 +16,7 @@ import os
import re import re
import time import time
from media import Media from .media import Media
class Video(Media): class Video(Media):

View File

@ -1,3 +1,4 @@
from __future__ import absolute_import
# Project imports # Project imports
import os import os
import re import re
@ -11,7 +12,7 @@ import mock
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))
import helper from . import helper
from elodie.filesystem import FileSystem from elodie.filesystem import FileSystem
from elodie.media.media import Media from elodie.media.media import Media
from elodie.media.photo import Photo from elodie.media.photo import Photo

View File

@ -1,3 +1,7 @@
from __future__ import absolute_import
from __future__ import division
from builtins import range
from past.utils import old_div
# Project imports # Project imports
import os import os
import random import random
@ -6,7 +10,7 @@ import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))
import helper from . import helper
from elodie import geolocation from elodie import geolocation
os.environ['TZ'] = 'GMT' os.environ['TZ'] = 'GMT'

View File

@ -1,3 +1,7 @@
from __future__ import division
from __future__ import unicode_literals
from builtins import range
from past.utils import old_div
import hashlib import hashlib
import os import os
import random import random
@ -12,7 +16,7 @@ from datetime import timedelta
def checksum(file_path, blocksize=65536): def checksum(file_path, blocksize=65536):
hasher = hashlib.sha256() hasher = hashlib.sha256()
with open(file_path, 'r') as f: with open(file_path, 'rb') as f:
buf = f.read(blocksize) buf = f.read(blocksize)
while len(buf) > 0: while len(buf) > 0:
@ -73,7 +77,7 @@ def random_decimal():
def random_coordinate(coordinate, precision): def random_coordinate(coordinate, precision):
# Here we add to the decimal section of the coordinate by a given precision # Here we add to the decimal section of the coordinate by a given precision
return coordinate + ((10.0 / (10.0**precision)) * random_decimal()) return coordinate + ((old_div(10.0, (10.0**precision))) * random_decimal())
def temp_dir(): def temp_dir():
return tempfile.gettempdir() return tempfile.gettempdir()
@ -91,8 +95,8 @@ def is_windows():
def path_tz_fix(file_name): def path_tz_fix(file_name):
if is_windows(): if is_windows():
# Calculate the offset between UTC and local time # Calculate the offset between UTC and local time
tz_shift = (datetime.fromtimestamp(0) - tz_shift = old_div((datetime.fromtimestamp(0) -
datetime.utcfromtimestamp(0)).seconds/3600 datetime.utcfromtimestamp(0)).seconds,3600)
# replace timestamp in file_name # replace timestamp in file_name
m = re.search('(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})',file_name) m = re.search('(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})',file_name)
t_date = datetime.fromtimestamp(time.mktime(time.strptime(m.group(0), '%Y-%m-%d_%H-%M-%S'))) t_date = datetime.fromtimestamp(time.mktime(time.strptime(m.group(0), '%Y-%m-%d_%H-%M-%S')))

View File

@ -1,10 +1,12 @@
from __future__ import print_function
from __future__ import absolute_import
# Project imports # Project imports
import os import os
import sys import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))) sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))))
import helper from . import helper
from elodie.localstorage import Db from elodie.localstorage import Db
from elodie import constants from elodie import constants
@ -154,10 +156,10 @@ def test_get_location_name_within_threshold():
latitude, longitude, name = helper.get_test_location() latitude, longitude, name = helper.get_test_location()
db.add_location(latitude, longitude, name) db.add_location(latitude, longitude, name)
print latitude print(latitude)
new_latitude = helper.random_coordinate(latitude, 4) new_latitude = helper.random_coordinate(latitude, 4)
new_longitude = helper.random_coordinate(longitude, 4) new_longitude = helper.random_coordinate(longitude, 4)
print new_latitude print(new_latitude)
# 10 miles # 10 miles
retrieved_name = db.get_location_name(new_latitude, new_longitude, 1600*10) retrieved_name = db.get_location_name(new_latitude, new_longitude, 1600*10)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 # -*- coding: utf-8
# Project imports # Project imports
from __future__ import print_function
import os import os
import sys import sys
@ -56,7 +57,6 @@ def test_get_date_taken():
audio = Audio(helper.get_file('audio.m4a')) audio = Audio(helper.get_file('audio.m4a'))
date_taken = audio.get_date_taken() date_taken = audio.get_date_taken()
print '%r' % date_taken
assert date_taken == (2016, 1, 4, 5, 24, 15, 0, 19, 0), date_taken assert date_taken == (2016, 1, 4, 5, 24, 15, 0, 19, 0), date_taken
def test_get_exiftool_attributes(): def test_get_exiftool_attributes():

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 # -*- coding: utf-8
# Project imports # Project imports
from __future__ import unicode_literals
import os import os
import sys import sys

View File

@ -1,3 +1,4 @@
click>=6.2,<7.0 click>=6.2,<7.0
requests>=2.9.1,<3.0 requests>=2.9.1,<3.0
send2trash>=1.3.0,<2.0 send2trash>=1.3.0,<2.0
future