commit
09872c82a5
|
@ -2,14 +2,15 @@ language: python
|
|||
dist: trusty
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.4"
|
||||
virtualenv:
|
||||
system_site_packages: true
|
||||
before_install:
|
||||
- "sudo apt-get update -qq"
|
||||
- "sudo apt-get install python-dev python-pip libimage-exiftool-perl -y"
|
||||
install:
|
||||
- "sudo pip install -r elodie/tests/requirements.txt"
|
||||
- "sudo pip install coveralls"
|
||||
- "pip install -r elodie/tests/requirements.txt"
|
||||
- "pip install coveralls"
|
||||
# command to run tests
|
||||
# test mapquest key
|
||||
before_script:
|
||||
|
@ -25,5 +26,5 @@ before_script:
|
|||
- "cd ${ELODIE_DIR}"
|
||||
after_success:
|
||||
- "coveralls"
|
||||
script:
|
||||
script:
|
||||
- "nosetests --with-coverage --cover-package=elodie -w elodie/tests"
|
||||
|
|
35
elodie.py
35
elodie.py
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
@ -35,16 +36,16 @@ def import_file(_file, destination, album_from_folder, trash):
|
|||
"""
|
||||
if not os.path.exists(_file):
|
||||
if constants.debug:
|
||||
print 'Could not find %s' % _file
|
||||
print '{"source":"%s", "error_msg":"Could not find %s"}' % \
|
||||
(_file, _file)
|
||||
print('Could not find %s' % _file)
|
||||
print('{"source":"%s", "error_msg":"Could not find %s"}' % \
|
||||
(_file, _file))
|
||||
return
|
||||
|
||||
media = Media.get_class_by_file(_file, [Text, Audio, Photo, Video])
|
||||
if not media:
|
||||
if constants.debug:
|
||||
print 'Not a supported file (%s)' % _file
|
||||
print '{"source":"%s", "error_msg":"Not a supported file"}' % _file
|
||||
print('Not a supported file (%s)' % _file)
|
||||
print('{"source":"%s", "error_msg":"Not a supported file"}' % _file)
|
||||
return
|
||||
|
||||
if media.__name__ == 'Video':
|
||||
|
@ -56,7 +57,7 @@ def import_file(_file, destination, album_from_folder, trash):
|
|||
dest_path = FILESYSTEM.process_file(_file, destination,
|
||||
media, allowDuplicate=False, move=False)
|
||||
if dest_path:
|
||||
print '%s -> %s' % (_file, dest_path)
|
||||
print('%s -> %s' % (_file, dest_path))
|
||||
if trash:
|
||||
send2trash(_file)
|
||||
|
||||
|
@ -109,9 +110,9 @@ def update_location(media, file_path, location_name):
|
|||
'latitude'], location_coords['longitude'])
|
||||
if not location_status:
|
||||
if constants.debug:
|
||||
print 'Failed to update location'
|
||||
print ('{"source":"%s",' % file_path,
|
||||
'"error_msg":"Failed to update location"}')
|
||||
print('Failed to update location')
|
||||
print(('{"source":"%s",' % file_path,
|
||||
'"error_msg":"Failed to update location"}'))
|
||||
sys.exit(1)
|
||||
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):
|
||||
msg = ('Invalid time format. Use YYYY-mm-dd hh:ii:ss or YYYY-mm-dd')
|
||||
if constants.debug:
|
||||
print msg
|
||||
print '{"source":"%s", "error_msg":"%s"}' % (file_path, msg)
|
||||
print(msg)
|
||||
print('{"source":"%s", "error_msg":"%s"}' % (file_path, msg))
|
||||
sys.exit(1)
|
||||
|
||||
time = datetime.strptime(time_string, time_format)
|
||||
|
@ -150,9 +151,9 @@ def _update(album, location, time, title, files):
|
|||
for file_path in files:
|
||||
if not os.path.exists(file_path):
|
||||
if constants.debug:
|
||||
print 'Could not find %s' % file_path
|
||||
print '{"source":"%s", "error_msg":"Could not find %s"}' % \
|
||||
(file_path, file_path)
|
||||
print('Could not find %s' % file_path)
|
||||
print('{"source":"%s", "error_msg":"Could not find %s"}' % \
|
||||
(file_path, file_path))
|
||||
continue
|
||||
|
||||
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,
|
||||
updated_media, move=True, allowDuplicate=True)
|
||||
if constants.debug:
|
||||
print u'%s -> %s' % (file_path, dest_path)
|
||||
print '{"source":"%s", "destination":"%s"}' % (file_path,
|
||||
dest_path)
|
||||
print(u'%s -> %s' % (file_path, dest_path))
|
||||
print('{"source":"%s", "destination":"%s"}' % (file_path,
|
||||
dest_path))
|
||||
# If the folder we moved the file out of or its parent are empty
|
||||
# we delete it.
|
||||
FILESYSTEM.delete_directory_if_empty(os.path.dirname(file_path))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
Command line argument parsing for helper scripts.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import getopt
|
||||
import sys
|
||||
|
@ -20,13 +21,13 @@ def parse(argv, options, long_options, usage):
|
|||
try:
|
||||
opts, args = getopt.getopt(argv, options, long_options)
|
||||
except getopt.GetoptError:
|
||||
print usage
|
||||
print(usage)
|
||||
sys.exit(2)
|
||||
|
||||
return_arguments = {}
|
||||
for opt, arg in opts:
|
||||
if opt == '-h':
|
||||
print usage
|
||||
print(usage)
|
||||
sys.exit()
|
||||
else:
|
||||
return_arguments[sub('^-+', '', opt)] = arg
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Helpers for checking for an interacting with external dependencies. These are
|
||||
things that Elodie requires, but aren't installed automatically for the user.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -43,7 +44,7 @@ def verify_dependencies():
|
|||
"""
|
||||
exiftool = get_exiftool()
|
||||
if exiftool is None:
|
||||
print >>sys.stderr, EXIFTOOL_ERROR
|
||||
print(EXIFTOOL_ERROR, file=sys.stderr)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -273,8 +273,7 @@ class ExifTool(object):
|
|||
"""
|
||||
if not self.running:
|
||||
raise ValueError("ExifTool instance not running.")
|
||||
cmd_txt = b"\n".join(params + (b"-execute\n",))
|
||||
self._process.stdin.write(cmd_txt.encode("utf-8"))
|
||||
self._process.stdin.write(b"\n".join(params + (b"-execute\n",)))
|
||||
self._process.stdin.flush()
|
||||
output = b""
|
||||
fd = self._process.stdout.fileno()
|
||||
|
@ -403,11 +402,13 @@ class ExifTool(object):
|
|||
"an iterable of strings")
|
||||
|
||||
params = []
|
||||
params_utf8 = []
|
||||
for tag, value in tags.items():
|
||||
params.append(u'-%s=%s' % (tag, value))
|
||||
|
||||
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):
|
||||
"""Writes the values of the specified tags for the given file.
|
||||
|
|
|
@ -3,6 +3,8 @@ General file system methods.
|
|||
|
||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from builtins import object
|
||||
|
||||
import os
|
||||
import re
|
||||
|
@ -176,7 +178,7 @@ class FileSystem(object):
|
|||
allow_duplicate = kwargs['allowDuplicate']
|
||||
|
||||
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
|
||||
|
||||
metadata = media.get_metadata()
|
||||
|
@ -191,17 +193,17 @@ class FileSystem(object):
|
|||
checksum = db.checksum(_file)
|
||||
if(checksum is None):
|
||||
if(constants.debug is True):
|
||||
print 'Could not get checksum for %s. Skipping...' % _file
|
||||
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(allow_duplicate is False and db.check_hash(checksum) is True):
|
||||
if(constants.debug is True):
|
||||
print '%s already exists at %s. Skipping...' % (
|
||||
print('%s already exists at %s. Skipping...' % (
|
||||
_file,
|
||||
db.get_hash(checksum)
|
||||
)
|
||||
))
|
||||
return
|
||||
|
||||
self.create_directory(dest_directory)
|
||||
|
|
|
@ -1,32 +1,23 @@
|
|||
"""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 ConfigParser import ConfigParser
|
||||
import fractions
|
||||
from configparser import ConfigParser
|
||||
|
||||
standard_library.install_aliases() # noqa
|
||||
|
||||
import requests
|
||||
import urllib
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
|
||||
from elodie import constants
|
||||
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):
|
||||
# Try to get cached location first
|
||||
db = Db()
|
||||
|
@ -88,7 +79,8 @@ def dms_to_decimal(degrees, minutes, seconds, direction=' '):
|
|||
if(direction[0] in 'WSws'):
|
||||
sign = -1
|
||||
return (
|
||||
float(degrees) + float(minutes) / 60 + float(seconds) / 3600
|
||||
float(degrees) + old_div(float(minutes), 60) +
|
||||
old_div(float(seconds), 3600)
|
||||
) * sign
|
||||
|
||||
|
||||
|
@ -159,17 +151,17 @@ def reverse_lookup(lat, lon):
|
|||
headers = {"Accept-Language": constants.accepted_language}
|
||||
r = requests.get(
|
||||
'http://open.mapquestapi.com/nominatim/v1/reverse.php?%s' %
|
||||
urllib.urlencode(params), headers=headers
|
||||
urllib.parse.urlencode(params), headers=headers
|
||||
)
|
||||
return r.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
if(constants.debug is True):
|
||||
print e
|
||||
print(e)
|
||||
return None
|
||||
except ValueError as e:
|
||||
if(constants.debug is True):
|
||||
print r.text
|
||||
print e
|
||||
print(r.text)
|
||||
print(e)
|
||||
return None
|
||||
|
||||
|
||||
|
@ -182,18 +174,18 @@ def lookup(name):
|
|||
try:
|
||||
params = {'format': 'json', 'key': key, 'location': name}
|
||||
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(
|
||||
'http://open.mapquestapi.com/geocoding/v1/address?%s' %
|
||||
urllib.urlencode(params)
|
||||
urllib.parse.urlencode(params)
|
||||
)
|
||||
return r.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
if(constants.debug is True):
|
||||
print e
|
||||
print(e)
|
||||
return None
|
||||
except ValueError as e:
|
||||
if(constants.debug is True):
|
||||
print r.text
|
||||
print e
|
||||
print(r.text)
|
||||
print(e)
|
||||
return None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""
|
||||
Methods for interacting with information Elodie caches about stored media.
|
||||
"""
|
||||
from builtins import map
|
||||
from builtins import object
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
|
@ -141,17 +143,17 @@ class Db(object):
|
|||
the given latitude and longitude.
|
||||
:returns: str, or None if a matching location couldn't be found.
|
||||
"""
|
||||
last_d = sys.maxint
|
||||
last_d = sys.maxsize
|
||||
name = None
|
||||
for data in self.location_db:
|
||||
# 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
|
||||
# convert decimal degrees to radians
|
||||
|
||||
lon1, lat1, lon2, lat2 = map(
|
||||
lon1, lat1, lon2, lat2 = list(map(
|
||||
radians,
|
||||
[longitude, latitude, data['long'], data['lat']]
|
||||
)
|
||||
))
|
||||
|
||||
r = 6371000 # radius of the earth in m
|
||||
x = (lon2 - lon1) * cos(0.5 * (lat2 + lat1))
|
||||
|
|
|
@ -5,8 +5,9 @@ class.
|
|||
|
||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from video import Video
|
||||
from .video import Video
|
||||
|
||||
|
||||
class Audio(Video):
|
||||
|
|
|
@ -13,6 +13,11 @@ are used to represent the actual files.
|
|||
import mimetypes
|
||||
import os
|
||||
|
||||
try: # Py3k compatibility
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = (bytes, str)
|
||||
|
||||
|
||||
class Base(object):
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ are used to represent the actual files.
|
|||
|
||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
# load modules
|
||||
from elodie import constants
|
||||
|
|
|
@ -4,6 +4,8 @@ image objects (JPG, DNG, etc.).
|
|||
|
||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
|
||||
import imghdr
|
||||
import os
|
||||
|
@ -15,7 +17,7 @@ from re import compile
|
|||
|
||||
|
||||
from elodie import constants
|
||||
from media import Media
|
||||
from .media import Media
|
||||
|
||||
|
||||
class Photo(Media):
|
||||
|
@ -95,7 +97,7 @@ class Photo(Media):
|
|||
break
|
||||
except BaseException as e:
|
||||
if(constants.debug is True):
|
||||
print e
|
||||
print(e)
|
||||
pass
|
||||
|
||||
if(seconds_since_epoch == 0):
|
||||
|
|
|
@ -123,7 +123,7 @@ class Text(Base):
|
|||
self.metadata_line = parsed_json
|
||||
except ValueError:
|
||||
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
|
||||
|
||||
def write_metadata(self, **kwargs):
|
||||
|
|
|
@ -4,6 +4,9 @@ objects (AVI, MOV, etc.).
|
|||
|
||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
# load modules
|
||||
from distutils.spawn import find_executable
|
||||
|
@ -13,7 +16,7 @@ import os
|
|||
import re
|
||||
import time
|
||||
|
||||
from media import Media
|
||||
from .media import Media
|
||||
|
||||
|
||||
class Video(Media):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
# Project imports
|
||||
import os
|
||||
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__))))))
|
||||
|
||||
import helper
|
||||
from . import helper
|
||||
from elodie.filesystem import FileSystem
|
||||
from elodie.media.media import Media
|
||||
from elodie.media.photo import Photo
|
||||
|
|
|
@ -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
|
||||
import os
|
||||
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__))))))
|
||||
|
||||
import helper
|
||||
from . import helper
|
||||
from elodie import geolocation
|
||||
|
||||
os.environ['TZ'] = 'GMT'
|
||||
|
@ -18,7 +22,7 @@ def test_decimal_to_dms():
|
|||
target_decimal_value = random.uniform(0.0, 180.0)
|
||||
if(x % 2 == 1):
|
||||
target_decimal_value = target_decimal_value * -1
|
||||
|
||||
|
||||
dms = geolocation.decimal_to_dms(target_decimal_value)
|
||||
|
||||
check_value = (dms[0] + dms[1] / 60 + dms[2] / 3600) * dms[3]
|
||||
|
@ -34,7 +38,7 @@ def test_dms_string_latitude():
|
|||
target_decimal_value = random.uniform(0.0, 180.0)
|
||||
if(x % 2 == 1):
|
||||
target_decimal_value = target_decimal_value * -1
|
||||
|
||||
|
||||
dms = geolocation.decimal_to_dms(target_decimal_value)
|
||||
dms_string = geolocation.dms_string(target_decimal_value, 'latitude')
|
||||
|
||||
|
@ -49,7 +53,7 @@ def test_dms_string_longitude():
|
|||
target_decimal_value = random.uniform(0.0, 180.0)
|
||||
if(x % 2 == 1):
|
||||
target_decimal_value = target_decimal_value * -1
|
||||
|
||||
|
||||
dms = geolocation.decimal_to_dms(target_decimal_value)
|
||||
dms_string = geolocation.dms_string(target_decimal_value, 'longitude')
|
||||
|
||||
|
|
|
@ -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 os
|
||||
import random
|
||||
|
@ -12,7 +16,7 @@ from datetime import timedelta
|
|||
|
||||
def checksum(file_path, blocksize=65536):
|
||||
hasher = hashlib.sha256()
|
||||
with open(file_path, 'r') as f:
|
||||
with open(file_path, 'rb') as f:
|
||||
buf = f.read(blocksize)
|
||||
|
||||
while len(buf) > 0:
|
||||
|
@ -73,7 +77,7 @@ def random_decimal():
|
|||
|
||||
def random_coordinate(coordinate, 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():
|
||||
return tempfile.gettempdir()
|
||||
|
@ -91,8 +95,8 @@ def is_windows():
|
|||
def path_tz_fix(file_name):
|
||||
if is_windows():
|
||||
# Calculate the offset between UTC and local time
|
||||
tz_shift = (datetime.fromtimestamp(0) -
|
||||
datetime.utcfromtimestamp(0)).seconds/3600
|
||||
tz_shift = old_div((datetime.fromtimestamp(0) -
|
||||
datetime.utcfromtimestamp(0)).seconds,3600)
|
||||
# replace timestamp in 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')))
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
# Project imports
|
||||
import os
|
||||
import sys
|
||||
|
||||
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 import constants
|
||||
|
||||
|
@ -154,10 +156,10 @@ def test_get_location_name_within_threshold():
|
|||
latitude, longitude, name = helper.get_test_location()
|
||||
db.add_location(latitude, longitude, name)
|
||||
|
||||
print latitude
|
||||
print(latitude)
|
||||
new_latitude = helper.random_coordinate(latitude, 4)
|
||||
new_longitude = helper.random_coordinate(longitude, 4)
|
||||
print new_latitude
|
||||
print(new_latitude)
|
||||
|
||||
# 10 miles
|
||||
retrieved_name = db.get_location_name(new_latitude, new_longitude, 1600*10)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8
|
||||
# Project imports
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
@ -56,7 +57,6 @@ def test_get_date_taken():
|
|||
audio = Audio(helper.get_file('audio.m4a'))
|
||||
date_taken = audio.get_date_taken()
|
||||
|
||||
print '%r' % date_taken
|
||||
assert date_taken == (2016, 1, 4, 5, 24, 15, 0, 19, 0), date_taken
|
||||
|
||||
def test_get_exiftool_attributes():
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8
|
||||
# Project imports
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
click>=6.2,<7.0
|
||||
requests>=2.9.1,<3.0
|
||||
send2trash>=1.3.0,<2.0
|
||||
future
|
||||
|
|
Loading…
Reference in New Issue