Skip to content

Commit

Permalink
Merge pull request #9 from sashacmc/1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sashacmc authored Nov 29, 2022
2 parents 6524a55 + d7b48c7 commit 32b832d
Show file tree
Hide file tree
Showing 14 changed files with 278 additions and 182 deletions.
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Command line tools for photo importing/renaming/rotating
* Media files scan
* Time when picture was taken detection (by EXIF, by file name, by file attributes)
* Media files moving/copying to configurable hierarchy
* Lossless rotations (via exiftran)
* Lossless rotations (via exiftran or jpegtran)

# Photo Importer Server
Standalone web server for fast media import for headless computer
Expand All @@ -18,31 +18,43 @@ Standalone web server for fast media import for headless computer
* Storages mount/unmount (via pmount)
* The same as photo-importer but without console

## Installation
# Installation

### Requirements:

* Python 3.3+
* Debian based Linux (Other Linux versions not officially supported, but might work)

### Supported OS:

* Debian based Linux (other Linux versions not officially supported, but might work)
* Windows 7 and above
* MacOS X and above (with brew installed, only console version)

### Dependencies:
* PyExifTool (pip3 install PyExifTool)
* python3-progressbar
* python3-psutil
* exiftran or jpegtran
* pmount (only for server)
* pypiwin32 (only for windows)
* [PyExifTool](https://pypi.org/project/PyExifTool/)
* [progressbar](https://pypi.org/project/progressbar/)
* [psutil](https://pypi.org/project/psutil/)
* [exiftran](https://linux.die.net/man/1/exiftran) or [jpegtran](https://linux.die.net/man/1/jpegtran)
* [pmount](https://linux.die.net/man/1/pmount) (only for server)
* [pypiwin32](https://pypi.org/project/pypiwin32/) (only for windows)


### Installation Options:

#### Installing via PyPi
```bash
sudo apt install exiftran exiftool pmount pip
sudo pip install photo-importer
```
#### Installing as debian package
```bash
debuild -b
sudo dpkg -i ../photo-importer_1.0.1_all.deb
sudo dpkg -i ../photo-importer_1.2.0_all.deb
```
#### Installing via setup.py
```bash
sudo apt install exiftran exiftool pmount pip
sudo pip install PyExifTool progressbar psutil
sudo python3 ./setup.py install
```

Expand All @@ -56,9 +68,9 @@ https://exiftool.org/
Download and extract jpegtran to photo_importer folder
http://sylvana.net/jpegcrop/jpegtran/

Install python dependencies
Install with python dependencies
```bash
python -m pip install progressbar psutil pyexiftool pypiwin32
python -m pip install pypiwin32 photo-importer
```

## Usage
Expand Down
13 changes: 13 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
photo-importer (1.2.0) stable; urgency=medium

* Add PyPi install support
* Update to new PyExifTool
* Reformat with black
* Update modules import
* Add importer test
* Improve fileprop tests
* Remove legacy modules __main__
* MacOS support verified

-- Alexander Bushnev <[email protected]> Tue, 29 Nov 2022 09:03:24 +0100

photo-importer (1.1.2) stable; urgency=medium

* Update setup scripts
Expand Down
11 changes: 9 additions & 2 deletions photo_importer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Config(object):
'out_path': '/mnt/multimedia/NEW/',
'in_path': '',
'log_file': 'photo-importer-server.log',
}
},
}

def __init__(self, filename=None, create=False):
Expand All @@ -44,7 +44,11 @@ def __init__(self, filename=None, create=False):

self.__config = configparser.ConfigParser()
self.__config.read_dict(self.DEFAULTS)
self.__config.read([filename, ])
self.__config.read(
[
filename,
]
)

if create:
self.__create_if_not_exists()
Expand All @@ -59,6 +63,9 @@ def __create_if_not_exists(self):
def __getitem__(self, sect):
return self.__config[sect]

def set(self, sect, name, val):
self.__config[sect][name] = val


if __name__ == "__main__":
Config(create=True)
61 changes: 39 additions & 22 deletions photo_importer/fileprop.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,27 @@


class FileProp(object):
DATE_REX = [
(re.compile('\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}'),
'%Y-%m-%d_%H-%M-%S'),
(re.compile('\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}'),
'%Y-%m-%d-%H-%M-%S'),
(re.compile('\d{4}-\d{2}-\d{2}T\d{2}.\d{2}.\d{2}'),
'%Y-%m-%dT%H.%M.%S'),
(re.compile('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'%Y-%m-%dT%H:%M:%S'),
(re.compile('\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}'),
'%Y_%m_%d_%H_%M_%S'),
DATE_REGEX = [
(
re.compile('\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}'),
'%Y-%m-%d_%H-%M-%S',
),
(
re.compile('\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}'),
'%Y-%m-%d-%H-%M-%S',
),
(
re.compile('\d{4}-\d{2}-\d{2}T\d{2}.\d{2}.\d{2}'),
'%Y-%m-%dT%H.%M.%S',
),
(
re.compile('\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'),
'%Y-%m-%dT%H:%M:%S',
),
(
re.compile('\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2}'),
'%Y_%m_%d_%H_%M_%S',
),
(re.compile('\d{8}_\d{6}'), '%Y%m%d_%H%M%S'),
(re.compile('\d{14}'), '%Y%m%d%H%M%S'),
(re.compile('\d{8}'), '%Y%m%d'),
Expand Down Expand Up @@ -55,14 +65,17 @@ class FileProp(object):
'QuickTime:MediaCreateDate',
'PDF:CreateDate',
'XMP:CreateDate',
'EXIF:CreateDate',
]

def __init__(self, config):
self.__config = config
self.__prepare_ext_to_type()
self.__out_list = set()
self.__exiftool = exiftool.ExifTool()
self.__exiftool.start()
self.__exiftool = exiftool.ExifToolHelper()

def __del__(self):
self.__exiftool.terminate()

def __prepare_ext_to_type(self):
self.EXT_TO_TYPE = {}
Expand Down Expand Up @@ -101,7 +114,7 @@ def __time(self, fullname, name, tp):
return None

def __time_by_name(self, fname):
for exp, fs in self.DATE_REX:
for exp, fs in self.DATE_REGEX:
mat = exp.findall(fname)
if len(mat):
try:
Expand All @@ -116,7 +129,7 @@ def __time_by_name(self, fname):

def __time_by_exif(self, fullname):
try:
metadata = self.__exiftool.get_metadata(fullname)
metadata = self.__exiftool.get_metadata(fullname)[0]
for tag in self.DATE_TAGS:
if tag in metadata:
md = metadata[tag]
Expand All @@ -125,8 +138,10 @@ def __time_by_exif(self, fullname):
md = md[0:pos]
return datetime.datetime.strptime(md, '%Y:%m:%d %H:%M:%S')

logging.warning('time by exif (%s) not found tags count: %s' %
(fullname, len(metadata)))
logging.warning(
'time by exif (%s) not found tags count: %s'
% (fullname, len(metadata))
)
for tag, val in metadata.items():
logging.debug('%s: %s' % (tag, val))
return None
Expand All @@ -136,7 +151,8 @@ def __time_by_exif(self, fullname):
def __time_by_attr(self, fullname):
try:
return datetime.datetime.fromtimestamp(
time.mktime(time.localtime(os.stat(fullname)[stat.ST_MTIME])))
time.mktime(time.localtime(os.stat(fullname)[stat.ST_MTIME]))
)
except (FileNotFoundError, KeyError) as ex:
logging.warning('time by attr (%s) exception: %s' % (fullname, ex))

Expand All @@ -161,13 +177,12 @@ def get(self, fullname):
ftime = self.__time(fullname, fname, tp)

if ftime:
out_name = ftime.strftime(
self.__config['main']['out_time_format'])
out_name = ftime.strftime(self.__config['main']['out_time_format'])
else:
out_name = None

if out_name:
ok = fname[0:len(out_name)] == out_name
ok = fname[0 : len(out_name)] == out_name
else:
ok = False

Expand Down Expand Up @@ -207,11 +222,13 @@ def out_name_full(self, path=None):
path = self.__path

return self.__prop_ptr._out_name_full(
path, self.__out_name, self.__ext)
path, self.__out_name, self.__ext
)


if __name__ == '__main__':
import sys

sys.path.insert(0, os.path.abspath('..'))

from photo_importer import log
Expand Down
15 changes: 5 additions & 10 deletions photo_importer/fileprop_test.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
#!/usr/bin/python3

import os
import sys
import unittest
import datetime

sys.path.insert(0, os.path.abspath('..'))

from photo_importer import config # noqa
from photo_importer import fileprop # noqa
from . import config
from . import fileprop


class TestFileProp(unittest.TestCase):
def setUp(self):
self.conf = config.Config()
self.fp = fileprop.FileProp(self.conf)
self.conf.set('main', 'time_src_image', 'name')
self.conf.set('main', 'time_src_video', 'name')
self.conf.set('main', 'time_src_audio', 'name')

# photo
def test_camera_photo(self):
Expand Down Expand Up @@ -145,7 +144,3 @@ def test_garbage(self):
self.assertEqual(fp.type(), fileprop.GARBAGE)
self.assertEqual(fp.time(), None)
self.assertEqual(fp.ok(), False)


if __name__ == '__main__':
unittest.main()
40 changes: 12 additions & 28 deletions photo_importer/importer.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
#!/usr/bin/python3

import os
import sys
import logging
import threading

sys.path.insert(0, os.path.abspath('..'))

from photo_importer import log # noqa
from photo_importer import mover # noqa
from photo_importer import config # noqa
from photo_importer import rotator # noqa
from photo_importer import fileprop # noqa
from . import log
from . import mover
from . import rotator
from . import fileprop


class Importer(threading.Thread):
Expand All @@ -28,8 +24,9 @@ def __init__(self, config, input_path, output_path, dryrun):

def run(self):
logging.info(
'Start: %s -> %s (dryrun: %s)' %
(self.__input_path, self.__output_path, self.__dryrun))
'Start: %s -> %s (dryrun: %s)'
% (self.__input_path, self.__output_path, self.__dryrun)
)

filenames, dirs = self.__scan_files(self.__input_path)

Expand All @@ -48,7 +45,8 @@ def __scan_files(self, input_path):
res_dir = []
res = []
for root, dirs, files in os.walk(
input_path, onerror=self.__on_walk_error):
input_path, onerror=self.__on_walk_error
):

for fname in files:
res.append(os.path.join(root, fname))
Expand All @@ -72,7 +70,8 @@ def __move_files(self, filenames):
self.__input_path,
self.__output_path,
filenames,
self.__dryrun)
self.__dryrun,
)
self.__stat['stage'] = 'move'

res = self.__mov.run()
Expand All @@ -88,10 +87,7 @@ def __image_filenames(self, move_result):

def __rotate_files(self, filenames):
logging.info('Rotating')
self.__rot = rotator.Rotator(
self.__config,
filenames,
self.__dryrun)
self.__rot = rotator.Rotator(self.__config, filenames, self.__dryrun)
self.__stat['stage'] = 'rotate'

self.__rot.run()
Expand All @@ -117,15 +113,3 @@ def status(self):

def log_text(self):
return self.__log.get_text()


if __name__ == '__main__':
import sys

log.initLogger()

imp = Importer(config.Config(), sys.argv[1], sys.argv[2], False)
imp.start()
imp.join()

print(imp.status())
Loading

0 comments on commit 32b832d

Please sign in to comment.