Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ orbs:

jobs:
build_and_test:
executor: python/default
docker:
- image: cimg/python:3.13
steps:
- checkout
- run:
Expand All @@ -28,11 +29,10 @@ jobs:
command: pytest elodie/tests -v --cov=elodie --cov-report=xml
- run:
name: Generate coverage
command: |
export COVERALLS_REPO_TOKEN=ID8gtfMwF9LYAukhtZHft0f9uo5Dg6TnU
coveralls
command: coveralls

workflows:
ci:
jobs:
- build_and_test
- build_and_test:
context: Environment
25 changes: 13 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
FROM debian:jessie
FROM python:3.9-slim

ENV DEBIAN_FRONTEND=noninteractive
ARG EXIF_TOOL_VER="13.27"

RUN apt-get update -y && \
apt-get install -y --no-install-recommends ca-certificates libimage-exiftool-perl python2.7 python-pip python-pyexiv2 wget make && \
pip install --upgrade pip setuptools && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
RUN apt-get update -y
RUN apt-get install -y --no-install-recommends ca-certificates libimage-exiftool-perl wget make
RUN rm -rf /var/lib/apt/lists/*

RUN apt-get update -qq && \
apt-get install -y locales -qq && \
Expand All @@ -19,10 +17,13 @@ ENV LANG C.UTF-8
ENV LANGUAGE C.UTF-8
ENV LC_ALL C.UTF-8

RUN 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 && \
make install && cd ../ && rm -r Image-ExifTool-10.20
RUN cd /tmp && \
wget https://exiftool.org/Image-ExifTool-${EXIF_TOOL_VER}.tar.gz && \
gzip -dc /tmp/Image-ExifTool-${EXIF_TOOL_VER}.tar.gz | tar -xf - && \
cd Image-ExifTool-${EXIF_TOOL_VER} && perl Makefile.PL && \
make install && \
cd ../ && rm -r Image-ExifTool-${EXIF_TOOL_VER} && \
rm -r Image-ExifTool-${EXIF_TOOL_VER}.tar.gz

COPY requirements.txt /opt/elodie/requirements.txt
COPY docs/requirements.txt /opt/elodie/docs/requirements.txt
Expand All @@ -34,4 +35,4 @@ RUN pip install -r docs/requirements.txt && \

COPY . /opt/elodie

CMD ["/bin/bash"]
ENTRYPOINT [ "/opt/elodie/elodie.py" ]
14 changes: 14 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,17 @@ If you're an english speaker then you will probably want to add `prefer_english_
## Questions, comments or concerns?

The best ways to provide feedback is by opening a [GitHub issue](https://github.com/jmathai/elodie/issues) or emailing me at [jaisen@jmathai.com](mailto:jaisen@jmathai.com).

## Using with docker

To avoid dealing with python versions and other requirements you can build docker image locally and tag it as `elodie`:

```
docker build . -t elodie
```

Then run `elodie` container:

```
docker run -v /path-to-destination:/dest -v /path-to-source:/src -v /path-to-config:/config -u $(id -u):$(id -g) -e ELODIE_APPLICATION_DIRECTORY=/config elodie import --debug --trash --destination=/dest /src
```
4 changes: 2 additions & 2 deletions elodie/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import os
import sys
from distutils.spawn import find_executable
import shutil


#: Error to print when exiftool can't be found.
Expand All @@ -25,7 +25,7 @@ def get_exiftool():

:returns: str or None
"""
path = find_executable('exiftool')
path = shutil.which('exiftool')
# If exiftool wasn't found we try to brute force the homebrew location
if path is None:
path = '/usr/local/bin/exiftool'
Expand Down
8 changes: 2 additions & 6 deletions elodie/geolocation.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
"""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

standard_library.install_aliases() # noqa

from os import path

Expand Down Expand Up @@ -84,8 +80,8 @@ def dms_to_decimal(degrees, minutes, seconds, direction=' '):
if(direction[0] in 'WSws'):
sign = -1
return (
float(degrees) + old_div(float(minutes), 60) +
old_div(float(seconds), 3600)
float(degrees) + (float(minutes) / 60) +
(float(seconds) / 3600)
) * sign


Expand Down
34 changes: 9 additions & 25 deletions elodie/media/photo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from __future__ import print_function
from __future__ import absolute_import

import imghdr
import os
import re
import time
Expand Down Expand Up @@ -72,7 +71,7 @@ def get_date_taken(self):
for key in self.exif_map['date_taken']:
try:
if(key in exif):
if(re.match('\d{4}(-|:)\d{2}(-|:)\d{2}', exif[key]) is not None): # noqa
if(re.match(r'\d{4}(-|:)\d{2}(-|:)\d{2}', exif[key]) is not None): # noqa
dt, tm = exif[key].split(' ')
dt_list = compile(r'-|:').split(dt)
dt_list = dt_list + compile(r'-|:').split(tm)
Expand Down Expand Up @@ -105,28 +104,13 @@ def is_valid(self):
if(extension != 'heic'):
# gh-4 This checks if the source file is an image.
# It doesn't validate against the list of supported types.
# We check with imghdr and pillow.
if(imghdr.what(source) is None):
# Pillow is used as a fallback and if it's not available we trust
# what imghdr returned.
if(self.pillow is None):
return False
else:
# imghdr won't detect all variants of images (https://bugs.python.org/issue28591)
# see https://github.com/jmathai/elodie/issues/281
# before giving up, we use `pillow` imaging library to detect file type
#
# It is important to note that the library doesn't decode or load the
# raster data unless it really has to. When you open a file,
# the file header is read to determine the file format and extract
# things like mode, size, and other properties required to decode the file,
# but the rest of the file is not processed until later.
try:
im = self.pillow.open(source)
except IOError:
return False

if(im.format is None):
return False
# We use pillow imaging library to detect file type
try:
im = self.pillow.open(source)
except IOError:
return False

if(im.format is None):
return False

return extension in self.extensions
8 changes: 4 additions & 4 deletions elodie/tests/dependencies_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from elodie.dependencies import get_exiftool


@mock.patch('elodie.dependencies.find_executable')
@mock.patch('elodie.dependencies.shutil')
@mock.patch('elodie.dependencies.os')
def test_exiftool(mock_os, mock_find_executable):
mock_find_executable.return_value = '/path/to/exiftool'
def test_exiftool(mock_os, mock_shutil):
mock_shutil.which.return_value = '/path/to/exiftool'
assert get_exiftool() == '/path/to/exiftool'

mock_find_executable.return_value = None
mock_shutil.which.return_value = None
mock_os.path.isfile.return_value = True
mock_os.path.access.return_value = True
assert get_exiftool() == '/usr/local/bin/exiftool'
Expand Down
7 changes: 5 additions & 2 deletions elodie/tests/elodie_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Project imports
from imp import load_source
import importlib.util
import unittest.mock as mock
import os
import sys
Expand All @@ -15,7 +15,10 @@
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))))

import helper
elodie = load_source('elodie', os.path.abspath('{}/../../elodie.py'.format(os.path.dirname(os.path.realpath(__file__)))))
elodie_path = os.path.abspath('{}/../../elodie.py'.format(os.path.dirname(os.path.realpath(__file__))))
spec = importlib.util.spec_from_file_location('elodie', elodie_path)
elodie = importlib.util.module_from_spec(spec)
spec.loader.exec_module(elodie)

from elodie.config import load_config
from elodie.localstorage import Db
Expand Down
1 change: 0 additions & 1 deletion elodie/tests/geolocation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from __future__ import division
from builtins import range
import pytest
from past.utils import old_div
# Project imports
import unittest.mock as mock
import os
Expand Down
11 changes: 5 additions & 6 deletions elodie/tests/helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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
Expand Down Expand Up @@ -98,7 +97,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 + ((old_div(10.0, (10.0**precision))) * random_decimal())
return coordinate + (((10.0 / (10.0**precision))) * random_decimal())

def temp_dir():
return tempfile.gettempdir()
Expand All @@ -116,13 +115,13 @@ def is_windows():
def path_tz_fix(file_name):
if is_windows():
# Calculate the offset between UTC and local time
tz_shift = old_div((datetime.fromtimestamp(0) -
datetime.utcfromtimestamp(0)).seconds,3600)
tz_shift = (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)
m = re.search(r'(\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')))
s_date_fix = (t_date-timedelta(hours=tz_shift)).strftime('%Y-%m-%d_%H-%M-%S')
return re.sub('\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}',s_date_fix,file_name)
return re.sub(r'\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}',s_date_fix,file_name)
else:
return file_name

Expand Down
5 changes: 0 additions & 5 deletions elodie/tests/media/photo_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,6 @@ def test_is_valid_fallback_using_pillow():

assert photo.is_valid()

def test_pillow_not_loaded():
photo = Photo(helper.get_file('imghdr-error.jpg'))
photo.pillow = None

assert photo.is_valid() == False

def test_set_album():
temporary_folder, folder = helper.create_working_folder()
Expand Down
7 changes: 4 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
click==6.6
requests==2.20.0
requests==2.32.5
urllib3==2.5.0
Send2Trash==1.3.0
future==0.16.0
configparser==3.5.0
tabulate==0.7.7
tabulate==0.9.0
Pillow==6.2.2; python_version == '2.7'
Pillow==10.4.0; python_version >= '3.6'
six==1.9
six>=1.16.0