diff --git a/.travis.yml b/.travis.yml index 7c3d554..59726e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ before_install: - "sudo apt-get update -qq" - "sudo apt-get install python-dev python-pip python-pyexiv2 libimage-exiftool-perl -y" install: - - "sudo pip install docopt LatLon nose requests" + - "sudo pip install docopt LatLon mock nose requests" # command to run tests # test mapquest key before_script: diff --git a/elodie.py b/elodie.py index 623d6fa..049540d 100755 --- a/elodie.py +++ b/elodie.py @@ -7,6 +7,12 @@ import sys from datetime import datetime from docopt import docopt +# Verify that external dependencies are present first, so the user gets a +# more user-friendly error instead of an ImportError traceback. +from elodie.dependencies import verify_dependencies +if not verify_dependencies(): + sys.exit(1) + from elodie import constants from elodie import geolocation from elodie.media.media import Media diff --git a/elodie/dependencies.py b/elodie/dependencies.py new file mode 100644 index 0000000..010a24f --- /dev/null +++ b/elodie/dependencies.py @@ -0,0 +1,61 @@ +"""Helpers for checking external dependencies.""" + +import os +import sys +from distutils.spawn import find_executable + + +EXIFTOOL_ERROR = u""" +It looks like you don't have exiftool installed, which Elodie requires. +Please take a look at the installation steps in the readme: + +https://github.com/jmathai/elodie#install-everything-you-need +""".lstrip() + +PYEXIV2_ERROR = u""" +{error_class_name}: {error} + +It looks like you don't have pyexiv2 installed, which Elodie requires for +geolocation. Please take a look at the installation steps in the readme: + +https://github.com/jmathai/elodie#install-everything-you-need +""".lstrip() + + +def get_exiftool(): + """Get path to executable exiftool binary. + + We wrap this since we call it in a few places and we do a fallback. + + @returns, None or string + """ + path = find_executable('exiftool') + # If exiftool wasn't found we try to brute force the homebrew location + if path is None: + path = '/usr/local/bin/exiftool' + if not os.path.isfile(path) or not os.access(path, os.X_OK): + return None + return path + + +def verify_dependencies(): + """Verify that external dependencies are installed. + + Prints a message to stderr and returns False if any dependencies are + missing. + + @returns, bool + """ + exiftool = get_exiftool() + if exiftool is None: + print >>sys.stderr, EXIFTOOL_ERROR + return False + + try: + import pyexiv2 + except ImportError as e: + print >>sys.stderr, PYEXIV2_ERROR.format( + error_class_name=e.__class__.__name__, error=e) + return False + + return True diff --git a/elodie/media/media.py b/elodie/media/media.py index 484305b..f309b87 100644 --- a/elodie/media/media.py +++ b/elodie/media/media.py @@ -4,8 +4,8 @@ Media package that's a parent class for media objects """ # load modules -from distutils.spawn import find_executable from elodie import constants +from elodie.dependencies import get_exiftool import mimetypes import os @@ -48,22 +48,6 @@ class Media(object): return exiftool_attributes['album'] - """ - Get path to executable exiftool binary. - We wrap this since we call it in a few places and we do a fallback. - - @returns, None or string - """ - def get_exiftool(self): - exiftool = find_executable('exiftool') - # If exiftool wasn't found we try to brute force the homebrew location - if(exiftool is None): - exiftool = '/usr/local/bin/exiftool' - if(not os.path.isfile(exiftool) or not os.access(exiftool, os.X_OK)): # noqa - return None - - return exiftool - """ Get the full path to the video. @@ -103,7 +87,7 @@ class Media(object): if(self.exiftool_attributes is not None): return self.exiftool_attributes - exiftool = self.get_exiftool() + exiftool = get_exiftool() if(exiftool is None): return False @@ -222,7 +206,7 @@ class Media(object): if(name is None): return False - exiftool = self.get_exiftool() + exiftool = get_exiftool() if(exiftool is None): return False diff --git a/elodie/media/video.py b/elodie/media/video.py index 9f1f74b..97e549e 100644 --- a/elodie/media/video.py +++ b/elodie/media/video.py @@ -16,6 +16,7 @@ import time from elodie import constants from elodie import plist_parser +from elodie.dependencies import get_exiftool from media import Media @@ -145,7 +146,7 @@ class Video(Media): @returns, string or None if exiftool is not found """ def get_exif(self): - exiftool = self.get_exiftool() + exiftool = get_exiftool() if(exiftool is None): return None diff --git a/elodie/tests/dependencies_test.py b/elodie/tests/dependencies_test.py new file mode 100644 index 0000000..0169239 --- /dev/null +++ b/elodie/tests/dependencies_test.py @@ -0,0 +1,18 @@ +import mock + +from elodie.dependencies import get_exiftool + + +@mock.patch('elodie.dependencies.find_executable') +@mock.patch('elodie.dependencies.os') +def test_exiftool(mock_os, mock_find_executable): + mock_find_executable.return_value = '/path/to/exiftool' + assert get_exiftool() == '/path/to/exiftool' + + mock_find_executable.return_value = None + mock_os.path.isfile.return_value = True + mock_os.path.access.return_value = True + assert get_exiftool() == '/usr/local/bin/exiftool' + + mock_os.path.isfile.return_value = False + assert get_exiftool() is None diff --git a/elodie/tests/media/media_test.py b/elodie/tests/media/media_test.py index 0ef94b5..3658b7a 100644 --- a/elodie/tests/media/media_test.py +++ b/elodie/tests/media/media_test.py @@ -21,11 +21,6 @@ from elodie.media.video import Video os.environ['TZ'] = 'GMT' -def test_exiftool(): - media = Media() - exiftool = media.get_exiftool() - - assert exiftool is not None, exiftool def test_get_file_path(): media = Media(helper.get_file('plain.jpg'))