Merge branch 'master' into improve-tests
This commit is contained in:
commit
ab77938408
|
@ -1,3 +1,4 @@
|
||||||
[report]
|
[report]
|
||||||
omit =
|
omit =
|
||||||
*/tests/*
|
*/tests/*
|
||||||
|
*/external/*
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
flake8 elodie --exclude=tests && nosetests -w elodie/tests
|
flake8 elodie --exclude=tests,external && nosetests -w elodie/tests
|
||||||
|
|
13
.travis.yml
13
.travis.yml
|
@ -2,19 +2,28 @@ 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:
|
||||||
- "mkdir ~/.elodie"
|
- "mkdir ~/.elodie"
|
||||||
- "sed 's/your-api-key-goes-here/x8wQLqGhW7qK3sFpjYtVTogVtoMK0S8s/g' config.ini-sample > ~/.elodie/config.ini"
|
- "sed 's/your-api-key-goes-here/x8wQLqGhW7qK3sFpjYtVTogVtoMK0S8s/g' config.ini-sample > ~/.elodie/config.ini"
|
||||||
|
# Get exiftool 10.20 installed
|
||||||
|
- "export ELODIE_DIR=${PWD}"
|
||||||
|
- "wget http://www.sno.phy.queensu.ca/~phil/exiftool/Image-ExifTool-10.20.tar.gz"
|
||||||
|
- "gzip -dc Image-ExifTool-10.20.tar.gz | tar -xf -"
|
||||||
|
- "cd Image-ExifTool-10.20"
|
||||||
|
- "perl Makefile.PL"
|
||||||
|
- "sudo make install"
|
||||||
|
- "cd ${ELODIE_DIR}"
|
||||||
after_success:
|
after_success:
|
||||||
- "coveralls"
|
- "coveralls"
|
||||||
script:
|
script:
|
||||||
|
|
10
Readme.md
10
Readme.md
|
@ -11,7 +11,7 @@ Getting started takes just a few minutes.
|
||||||
|
|
||||||
Elodie relies on the great [ExifTool library by Phil Harvey](http://www.sno.phy.queensu.ca/~phil/exiftool/). You'll need to install it for your platform.
|
Elodie relies on the great [ExifTool library by Phil Harvey](http://www.sno.phy.queensu.ca/~phil/exiftool/). You'll need to install it for your platform.
|
||||||
|
|
||||||
Some features for video files will only work with newer versions of ExifTool and have been tested on version 10.15 or higher. Check your version by typing `exiftool -ver` and see the [manual installation instructions for ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/install.html#Unix) if needed.
|
Some features for video files will only work with newer versions of ExifTool and have been tested on version 10.20 or higher. Check your version by typing `exiftool -ver` and see the [manual installation instructions for ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/install.html#Unix) if needed.
|
||||||
|
|
||||||
```
|
```
|
||||||
# OSX (uses homebrew, http://brew.sh/)
|
# OSX (uses homebrew, http://brew.sh/)
|
||||||
|
@ -98,7 +98,7 @@ Here's an example of a very asynchronous setup.
|
||||||
* Periodically recategorize photos by fixing their location or date or by adding them to an album.
|
* Periodically recategorize photos by fixing their location or date or by adding them to an album.
|
||||||
* Have a Synology at home set to automatically sync down from Dropbox/Google Drive.
|
* Have a Synology at home set to automatically sync down from Dropbox/Google Drive.
|
||||||
|
|
||||||
This setup means you can quickly get photos off your or anyone's phone and know that they'll be organized and backed up in 3 locations by the time they're ready to view them.
|
This setup means you can quickly get photos off your or anyone's phone and know that they'll be organized and backed up in 3 locations by the time you're ready to view them.
|
||||||
|
|
||||||
<p align="center"><img src ="creative/workflow-simplified-white-bg.png" /></p>
|
<p align="center"><img src ="creative/workflow-simplified-white-bg.png" /></p>
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ Organizing your existing photos is great. But I'd be lying if I said I was the o
|
||||||
|
|
||||||
In order to sort new photos that I haven't already organized I need someone to tell me about them. There's no single way to do this. You could use inotify, cron, Automator or my favorite app - Hazel; it doesn't matter.
|
In order to sort new photos that I haven't already organized I need someone to tell me about them. There's no single way to do this. You could use inotify, cron, Automator or my favorite app - Hazel; it doesn't matter.
|
||||||
|
|
||||||
If you'd like to let me know of a specific photo or group of photos to add to your library you would run one of the following command. Use fully qualified paths for everything since you won't be running this manually.
|
If you'd like to let me know of a specific photo or group of photos to add to your library you would run one of the following commands. Use fully qualified paths for everything since you won't be running this manually.
|
||||||
|
|
||||||
```
|
```
|
||||||
# I can import a single file into your library.
|
# I can import a single file into your library.
|
||||||
|
@ -219,13 +219,13 @@ When I organize photos I look at the embedded metadata. Here are the details of
|
||||||
|
|
||||||
| Dimension | Fields | Notes |
|
| Dimension | Fields | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Date Taken (photo) | EXIF:DateTimeOriginal,EXIF:DateTime, EXIF:DateTimeDigitized, file created, file modified | |
|
| Date Taken (photo) | EXIF:DateTimeOriginal, EXIF:CreateDate, EXIF:ModifyDate, file created, file modified | |
|
||||||
| Date Taken (video, audio) | QuickTime:CreationDate, QuickTime:CreationDate-und-US, QuickTime:MediaCreateDate, file created, file modified | |
|
| Date Taken (video, audio) | QuickTime:CreationDate, QuickTime:CreationDate-und-US, QuickTime:MediaCreateDate, file created, file modified | |
|
||||||
| Location (photo) | EXIF:GPSLatitude/EXIF:GPSLatitudeRef, EXIF:GPSLongitude/EXIF:GPSLongitudeRef | |
|
| Location (photo) | EXIF:GPSLatitude/EXIF:GPSLatitudeRef, EXIF:GPSLongitude/EXIF:GPSLongitudeRef | |
|
||||||
| Location (video, audio) | XMP:GPSLatitude, Composite:GPSLatitude, XMP:GPSLongitude, Composite:GPSLongitude | Composite tags are read-only |
|
| Location (video, audio) | XMP:GPSLatitude, Composite:GPSLatitude, XMP:GPSLongitude, Composite:GPSLongitude | Composite tags are read-only |
|
||||||
| Title (photo) | XMP:Title | |
|
| Title (photo) | XMP:Title | |
|
||||||
| Title (video, audio) | XMP:DisplayName | |
|
| Title (video, audio) | XMP:DisplayName | |
|
||||||
| Album | XMP:Album | User defined tag in `configs/ExifTool_config` |
|
| Album | XMP-xmpDM:Album, XMP:Album | XMP:Album is user defined in `configs/ExifTool_config` for backwards compatability |
|
||||||
|
|
||||||
## Using OpenStreetMap data from MapQuest
|
## Using OpenStreetMap data from MapQuest
|
||||||
|
|
||||||
|
|
35
elodie.py
35
elodie.py
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -76,11 +67,11 @@ def coordinates_by_name(name):
|
||||||
def decimal_to_dms(decimal):
|
def decimal_to_dms(decimal):
|
||||||
decimal = float(decimal)
|
decimal = float(decimal)
|
||||||
decimal_abs = abs(decimal)
|
decimal_abs = abs(decimal)
|
||||||
minutes,seconds = divmod(decimal_abs*3600,60)
|
minutes, seconds = divmod(decimal_abs*3600, 60)
|
||||||
degrees,minutes = divmod(minutes,60)
|
degrees, minutes = divmod(minutes, 60)
|
||||||
degrees = degrees
|
degrees = degrees
|
||||||
sign = 1 if decimal >= 0 else -1
|
sign = 1 if decimal >= 0 else -1
|
||||||
return (degrees,minutes,seconds, sign)
|
return (degrees, minutes, seconds, sign)
|
||||||
|
|
||||||
|
|
||||||
def dms_to_decimal(degrees, minutes, seconds, direction=' '):
|
def dms_to_decimal(degrees, minutes, seconds, direction=' '):
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -15,10 +16,6 @@ from elodie.dependencies import get_exiftool
|
||||||
from elodie.external.pyexiftool import ExifTool
|
from elodie.external.pyexiftool import ExifTool
|
||||||
from elodie.media.base import Base
|
from elodie.media.base import Base
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
class Media(Base):
|
class Media(Base):
|
||||||
|
|
||||||
|
@ -39,11 +36,11 @@ class Media(Base):
|
||||||
self.exif_map = {
|
self.exif_map = {
|
||||||
'date_taken': [
|
'date_taken': [
|
||||||
'EXIF:DateTimeOriginal',
|
'EXIF:DateTimeOriginal',
|
||||||
'EXIF:DateTime',
|
'EXIF:CreateDate',
|
||||||
'EXIF:DateTimeDigitized'
|
'EXIF:ModifyDate'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
self.album_key = 'XMP:Album'
|
self.album_keys = ['XMP-xmpDM:Album', 'XMP:Album']
|
||||||
self.title_key = 'XMP:Title'
|
self.title_key = 'XMP:Title'
|
||||||
self.latitude_keys = ['EXIF:GPSLatitude']
|
self.latitude_keys = ['EXIF:GPSLatitude']
|
||||||
self.longitude_keys = ['EXIF:GPSLongitude']
|
self.longitude_keys = ['EXIF:GPSLongitude']
|
||||||
|
@ -68,10 +65,11 @@ class Media(Base):
|
||||||
if exiftool_attributes is None:
|
if exiftool_attributes is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.album_key not in exiftool_attributes:
|
for album_key in self.album_keys:
|
||||||
return None
|
if album_key in exiftool_attributes:
|
||||||
|
return exiftool_attributes[album_key]
|
||||||
|
|
||||||
return exiftool_attributes[self.album_key]
|
return None
|
||||||
|
|
||||||
def get_coordinate(self, type='latitude'):
|
def get_coordinate(self, type='latitude'):
|
||||||
"""Get latitude or longitude of media from EXIF
|
"""Get latitude or longitude of media from EXIF
|
||||||
|
@ -92,12 +90,16 @@ class Media(Base):
|
||||||
for key in self.latitude_keys + self.longitude_keys:
|
for key in self.latitude_keys + self.longitude_keys:
|
||||||
# TODO: verify that we need to check ref key
|
# TODO: verify that we need to check ref key
|
||||||
# when self.set_gps_ref != True
|
# when self.set_gps_ref != True
|
||||||
if type == 'latitude' and key in self.latitude_keys and key in exif:
|
if type == 'latitude' and key in self.latitude_keys and \
|
||||||
if self.latitude_ref_key in exif and exif[self.latitude_ref_key] == 'S': #noqa
|
key in exif:
|
||||||
|
if self.latitude_ref_key in exif and \
|
||||||
|
exif[self.latitude_ref_key] == 'S':
|
||||||
direction_multiplier = -1
|
direction_multiplier = -1
|
||||||
return exif[key] * direction_multiplier
|
return exif[key] * direction_multiplier
|
||||||
elif type == 'longitude' and key in self.longitude_keys and key in exif: #noqa
|
elif type == 'longitude' and key in self.longitude_keys and \
|
||||||
if self.longitude_ref_key in exif and exif[self.longitude_ref_key] == 'W': #noqa
|
key in exif:
|
||||||
|
if self.longitude_ref_key in exif and \
|
||||||
|
exif[self.longitude_ref_key] == 'W':
|
||||||
direction_multiplier = -1
|
direction_multiplier = -1
|
||||||
return exif[key] * direction_multiplier
|
return exif[key] * direction_multiplier
|
||||||
|
|
||||||
|
@ -153,9 +155,7 @@ class Media(Base):
|
||||||
if(not self.is_valid()):
|
if(not self.is_valid()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
source = self.source
|
tags = {self.album_keys[0]: album}
|
||||||
|
|
||||||
tags = {self.album_key: album}
|
|
||||||
status = self.__set_tags(tags)
|
status = self.__set_tags(tags)
|
||||||
self.reset_cache()
|
self.reset_cache()
|
||||||
|
|
||||||
|
@ -170,8 +170,6 @@ class Media(Base):
|
||||||
if(time is None):
|
if(time is None):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
source = self.source
|
|
||||||
|
|
||||||
tags = {}
|
tags = {}
|
||||||
formatted_time = time.strftime('%Y:%m:%d %H:%M:%S')
|
formatted_time = time.strftime('%Y:%m:%d %H:%M:%S')
|
||||||
for key in self.exif_map['date_taken']:
|
for key in self.exif_map['date_taken']:
|
||||||
|
@ -185,8 +183,6 @@ class Media(Base):
|
||||||
if(not self.is_valid()):
|
if(not self.is_valid()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
source = self.source
|
|
||||||
|
|
||||||
# The lat/lon _keys array has an order of precedence.
|
# The lat/lon _keys array has an order of precedence.
|
||||||
# The first key is writable and we will give the writable
|
# The first key is writable and we will give the writable
|
||||||
# key precence when reading.
|
# key precence when reading.
|
||||||
|
@ -222,8 +218,6 @@ class Media(Base):
|
||||||
if(title is None):
|
if(title is None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
source = self.source
|
|
||||||
|
|
||||||
tags = {self.title_key: title}
|
tags = {self.title_key: title}
|
||||||
status = self.__set_tags(tags)
|
status = self.__set_tags(tags)
|
||||||
self.reset_cache()
|
self.reset_cache()
|
||||||
|
|
|
@ -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,9 +17,7 @@ from re import compile
|
||||||
|
|
||||||
|
|
||||||
from elodie import constants
|
from elodie import constants
|
||||||
from elodie import geolocation
|
from .media import Media
|
||||||
from elodie.external.pyexiftool import ExifTool
|
|
||||||
from media import Media
|
|
||||||
|
|
||||||
|
|
||||||
class Photo(Media):
|
class Photo(Media):
|
||||||
|
@ -80,7 +80,7 @@ class Photo(Media):
|
||||||
# We need to parse a string from EXIF into a timestamp.
|
# We need to parse a string from EXIF into a timestamp.
|
||||||
# EXIF DateTimeOriginal and EXIF DateTime are both stored
|
# EXIF DateTimeOriginal and EXIF DateTime are both stored
|
||||||
# in %Y:%m:%d %H:%M:%S format
|
# in %Y:%m:%d %H:%M:%S format
|
||||||
# we use split on a space and then r':|-' -> convert to int -> .timetuple()
|
# we split on a space and then r':|-' -> convert to int -> .timetuple()
|
||||||
# the conversion in the local timezone
|
# the conversion in the local timezone
|
||||||
# EXIF DateTime is already stored as a timestamp
|
# EXIF DateTime is already stored as a timestamp
|
||||||
# Sourced from https://github.com/photo/frontend/blob/master/src/libraries/models/Photo.php#L500 # noqa
|
# Sourced from https://github.com/photo/frontend/blob/master/src/libraries/models/Photo.php#L500 # noqa
|
||||||
|
@ -97,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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -4,23 +4,19 @@ 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
|
||||||
import tempfile
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from elodie import constants
|
from .media import Media
|
||||||
from elodie import plist_parser
|
|
||||||
from elodie.dependencies import get_exiftool
|
|
||||||
from media import Base
|
|
||||||
from media import Media
|
|
||||||
|
|
||||||
|
|
||||||
class Video(Media):
|
class Video(Media):
|
||||||
|
@ -45,12 +41,12 @@ class Video(Media):
|
||||||
self.title_key = 'XMP:DisplayName'
|
self.title_key = 'XMP:DisplayName'
|
||||||
self.latitude_keys = [
|
self.latitude_keys = [
|
||||||
'XMP:GPSLatitude',
|
'XMP:GPSLatitude',
|
||||||
#'QuickTime:GPSLatitude',
|
# 'QuickTime:GPSLatitude',
|
||||||
'Composite:GPSLatitude'
|
'Composite:GPSLatitude'
|
||||||
]
|
]
|
||||||
self.longitude_keys = [
|
self.longitude_keys = [
|
||||||
'XMP:GPSLongitude',
|
'XMP:GPSLongitude',
|
||||||
#'QuickTime:GPSLongitude',
|
# 'QuickTime:GPSLongitude',
|
||||||
'Composite:GPSLongitude'
|
'Composite:GPSLongitude'
|
||||||
]
|
]
|
||||||
self.latitude_ref_key = 'EXIF:GPSLatitudeRef'
|
self.latitude_ref_key = 'EXIF:GPSLatitudeRef'
|
||||||
|
@ -107,7 +103,7 @@ class Video(Media):
|
||||||
if date_offset is not None:
|
if date_offset is not None:
|
||||||
offset_parts = date_offset[1:].split(':')
|
offset_parts = date_offset[1:].split(':')
|
||||||
offset_seconds = int(offset_parts[0]) * 3600
|
offset_seconds = int(offset_parts[0]) * 3600
|
||||||
offset_seconds = offset_seconds + int(offset_parts[1]) * 60 #noqa
|
offset_seconds = offset_seconds + int(offset_parts[1]) * 60 # noqa
|
||||||
if date_offset[0] == '-':
|
if date_offset[0] == '-':
|
||||||
seconds_since_epoch - offset_seconds
|
seconds_since_epoch - offset_seconds
|
||||||
elif date_offset[0] == '+':
|
elif date_offset[0] == '+':
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
"""
|
|
||||||
Parse OS X plists.
|
|
||||||
|
|
||||||
.. moduleauthor:: Jaisen Mathai <jaisen@jmathai.com>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# load modules
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
import plistlib
|
|
||||||
|
|
||||||
|
|
||||||
class Plist(object):
|
|
||||||
|
|
||||||
"""Parse and interact with a plist file.
|
|
||||||
|
|
||||||
This class wraps the `plistlib module`_ from the standard library.
|
|
||||||
|
|
||||||
.. _plistlib module: https://docs.python.org/3/library/plistlib.html
|
|
||||||
|
|
||||||
:param str source: Source to read the plist from.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, source):
|
|
||||||
if not path.isfile(source):
|
|
||||||
raise IOError('Could not load plist file %s' % source)
|
|
||||||
self.source = source
|
|
||||||
self.plist = plistlib.readPlist(self.source)
|
|
||||||
|
|
||||||
def update_key(self, key, value):
|
|
||||||
"""Update a value in the plist.
|
|
||||||
|
|
||||||
:param str key: Key to modify.
|
|
||||||
:param value: New value.
|
|
||||||
"""
|
|
||||||
self.plist[key] = value
|
|
||||||
|
|
||||||
def write_file(self, destination):
|
|
||||||
"""Save the plist.
|
|
||||||
|
|
||||||
:param destination: Write the plist here.
|
|
||||||
:type destination: str or file object
|
|
||||||
"""
|
|
||||||
plistlib.writePlist(self.plist, destination)
|
|
|
@ -87,9 +87,6 @@ def test_import_file_video():
|
||||||
assert helper.path_tz_fix(os.path.join('2015-01-Jan','California','2015-01-19_12-45-11-video.mov')) in dest_path, dest_path
|
assert helper.path_tz_fix(os.path.join('2015-01-Jan','California','2015-01-19_12-45-11-video.mov')) in dest_path, dest_path
|
||||||
|
|
||||||
def test_update_location_on_audio():
|
def test_update_location_on_audio():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -165,9 +162,6 @@ def test_update_location_on_text():
|
||||||
assert helper.isclose(metadata_processed['longitude'], -122.03635), metadata_processed['longitude']
|
assert helper.isclose(metadata_processed['longitude'], -122.03635), metadata_processed['longitude']
|
||||||
|
|
||||||
def test_update_location_on_video():
|
def test_update_location_on_video():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -193,9 +187,6 @@ def test_update_location_on_video():
|
||||||
assert helper.isclose(metadata_processed['longitude'], -122.03635), metadata_processed['longitude']
|
assert helper.isclose(metadata_processed['longitude'], -122.03635), metadata_processed['longitude']
|
||||||
|
|
||||||
def test_update_time_on_audio():
|
def test_update_time_on_audio():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -268,9 +259,6 @@ def test_update_time_on_text():
|
||||||
assert metadata_processed['date_taken'] == helper.time_convert((2000, 1, 1, 12, 0, 0, 5, 1, 0)), metadata_processed['date_taken']
|
assert metadata_processed['date_taken'] == helper.time_convert((2000, 1, 1, 12, 0, 0, 5, 1, 0)), metadata_processed['date_taken']
|
||||||
|
|
||||||
def test_update_time_on_video():
|
def test_update_time_on_video():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
temporary_folder_destination, folder_destination = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -303,7 +291,3 @@ def restore_hash_db():
|
||||||
hash_db = '{}-test'.format(constants.hash_db)
|
hash_db = '{}-test'.format(constants.hash_db)
|
||||||
if os.path.isfile(hash_db):
|
if os.path.isfile(hash_db):
|
||||||
os.rename(hash_db, hash_db.replace('-test', ''))
|
os.rename(hash_db, hash_db.replace('-test', ''))
|
||||||
|
|
||||||
def can_edit_exif():
|
|
||||||
video = Video()
|
|
||||||
return video.get_avmetareadwrite()
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -342,9 +343,6 @@ def test_process_file_with_album_and_title_and_location():
|
||||||
|
|
||||||
# gh-89 (setting album then title reverts album)
|
# gh-89 (setting album then title reverts album)
|
||||||
def test_process_video_with_album_then_title():
|
def test_process_video_with_album_then_title():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
filesystem = FileSystem()
|
filesystem = FileSystem()
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -366,7 +364,3 @@ def test_process_video_with_album_then_title():
|
||||||
assert origin_checksum is not None, origin_checksum
|
assert origin_checksum is not None, origin_checksum
|
||||||
assert origin_checksum != destination_checksum, destination_checksum
|
assert origin_checksum != destination_checksum, destination_checksum
|
||||||
assert helper.path_tz_fix(os.path.join('2015-01-Jan','test_album','2015-01-19_12-45-11-movie-test_title.mov')) in destination, destination
|
assert helper.path_tz_fix(os.path.join('2015-01-Jan','test_album','2015-01-19_12-45-11-movie-test_title.mov')) in destination, destination
|
||||||
|
|
||||||
def can_edit_exif():
|
|
||||||
video = Video()
|
|
||||||
return video.get_avmetareadwrite()
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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')))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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():
|
||||||
|
@ -77,9 +77,6 @@ def test_is_not_valid():
|
||||||
assert not audio.is_valid()
|
assert not audio.is_valid()
|
||||||
|
|
||||||
def test_set_date_taken():
|
def test_set_date_taken():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
origin = '%s/audio.m4a' % folder
|
origin = '%s/audio.m4a' % folder
|
||||||
|
@ -100,9 +97,6 @@ def test_set_date_taken():
|
||||||
assert date_taken == (2013, 9, 30, 7, 6, 5, 0, 273, 0), metadata['date_taken']
|
assert date_taken == (2013, 9, 30, 7, 6, 5, 0, 273, 0), metadata['date_taken']
|
||||||
|
|
||||||
def test_set_location():
|
def test_set_location():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
origin = '%s/audio.m4a' % folder
|
origin = '%s/audio.m4a' % folder
|
||||||
|
@ -129,9 +123,6 @@ def test_set_location():
|
||||||
assert helper.isclose(metadata['longitude'], 99.9999999999), metadata['longitude']
|
assert helper.isclose(metadata['longitude'], 99.9999999999), metadata['longitude']
|
||||||
|
|
||||||
def test_set_location_minus():
|
def test_set_location_minus():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
origin = '%s/audio.m4a' % folder
|
origin = '%s/audio.m4a' % folder
|
||||||
|
@ -158,9 +149,6 @@ def test_set_location_minus():
|
||||||
assert helper.isclose(metadata['longitude'], -99.999999), metadata['longitude']
|
assert helper.isclose(metadata['longitude'], -99.999999), metadata['longitude']
|
||||||
|
|
||||||
def test_set_title():
|
def test_set_title():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
origin = '%s/audio.m4a' % folder
|
origin = '%s/audio.m4a' % folder
|
||||||
|
@ -181,9 +169,6 @@ def test_set_title():
|
||||||
assert metadata['title'] == 'my audio title', metadata['title']
|
assert metadata['title'] == 'my audio title', metadata['title']
|
||||||
|
|
||||||
def test_set_title_non_ascii():
|
def test_set_title_non_ascii():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
raise SkipTest('gh-27, non-ascii characters')
|
raise SkipTest('gh-27, non-ascii characters')
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -203,7 +188,3 @@ def test_set_title_non_ascii():
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
assert metadata['title'] == '形声字 / 形聲字', metadata['title']
|
assert metadata['title'] == '形声字 / 形聲字', metadata['title']
|
||||||
|
|
||||||
def can_edit_exif():
|
|
||||||
audio = Audio()
|
|
||||||
return audio.get_avmetareadwrite()
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -101,9 +101,6 @@ def test_set_album():
|
||||||
assert metadata_new['album'] == 'Test Album', metadata_new['album']
|
assert metadata_new['album'] == 'Test Album', metadata_new['album']
|
||||||
|
|
||||||
def test_set_date_taken():
|
def test_set_date_taken():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
origin = '%s/video.mov' % folder
|
origin = '%s/video.mov' % folder
|
||||||
|
@ -124,9 +121,6 @@ def test_set_date_taken():
|
||||||
assert date_taken == (2013, 9, 30, 7, 6, 5, 0, 273, 0), metadata['date_taken']
|
assert date_taken == (2013, 9, 30, 7, 6, 5, 0, 273, 0), metadata['date_taken']
|
||||||
|
|
||||||
def test_set_location():
|
def test_set_location():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
origin = '%s/video.mov' % folder
|
origin = '%s/video.mov' % folder
|
||||||
|
@ -153,9 +147,6 @@ def test_set_location():
|
||||||
assert helper.isclose(metadata['longitude'], 99.9999999999), metadata['longitude']
|
assert helper.isclose(metadata['longitude'], 99.9999999999), metadata['longitude']
|
||||||
|
|
||||||
def test_set_title():
|
def test_set_title():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
origin = '%s/video.mov' % folder
|
origin = '%s/video.mov' % folder
|
||||||
|
@ -176,9 +167,6 @@ def test_set_title():
|
||||||
assert metadata['title'] == 'my video title', metadata['title']
|
assert metadata['title'] == 'my video title', metadata['title']
|
||||||
|
|
||||||
def test_set_title_non_ascii():
|
def test_set_title_non_ascii():
|
||||||
if not can_edit_exif():
|
|
||||||
raise SkipTest('avmetareadwrite executable not found')
|
|
||||||
|
|
||||||
raise SkipTest('gh-27, non-ascii characters')
|
raise SkipTest('gh-27, non-ascii characters')
|
||||||
temporary_folder, folder = helper.create_working_folder()
|
temporary_folder, folder = helper.create_working_folder()
|
||||||
|
|
||||||
|
@ -198,7 +186,3 @@ def test_set_title_non_ascii():
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(folder)
|
||||||
|
|
||||||
assert metadata['title'] == '形声字 / 形聲字', metadata['title']
|
assert metadata['title'] == '形声字 / 形聲字', metadata['title']
|
||||||
|
|
||||||
def can_edit_exif():
|
|
||||||
video = Video()
|
|
||||||
return video.get_avmetareadwrite()
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue