From b30fcf344e75684951dd2367ebd07a948fc28cec Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 00:01:16 +0100 Subject: [PATCH 01/17] Update to new PyExifTool --- photo_importer/fileprop.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/photo_importer/fileprop.py b/photo_importer/fileprop.py index 35f63d6..11380f0 100755 --- a/photo_importer/fileprop.py +++ b/photo_importer/fileprop.py @@ -55,14 +55,14 @@ 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 __prepare_ext_to_type(self): self.EXT_TO_TYPE = {} @@ -116,7 +116,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] From 3b157d32b7d570f6d9a7f854ea08786218dd8384 Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 00:09:25 +0100 Subject: [PATCH 02/17] Reformat with black black --skip-string-normalization --line-length=80 --- photo_importer/config.py | 8 ++++-- photo_importer/fileprop.py | 48 ++++++++++++++++++++------------ photo_importer/importer.py | 16 +++++------ photo_importer/mover.py | 14 ++++++---- photo_importer/rotator.py | 39 ++++++++++++++++---------- photo_importer/run.py | 24 ++++++++-------- photo_importer/server.py | 57 +++++++++++++++++++------------------- 7 files changed, 119 insertions(+), 87 deletions(-) diff --git a/photo_importer/config.py b/photo_importer/config.py index 83b9059..5c4579b 100755 --- a/photo_importer/config.py +++ b/photo_importer/config.py @@ -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): @@ -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() diff --git a/photo_importer/fileprop.py b/photo_importer/fileprop.py index 11380f0..43d1370 100755 --- a/photo_importer/fileprop.py +++ b/photo_importer/fileprop.py @@ -18,16 +18,26 @@ 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'), + ( + 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'), @@ -125,8 +135,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 @@ -136,7 +148,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)) @@ -161,13 +174,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 @@ -206,12 +218,12 @@ def out_name_full(self, path=None): if path is None: path = self.__path - return self.__prop_ptr._out_name_full( - path, self.__out_name, self.__ext) + return self.__prop_ptr._out_name_full(path, self.__out_name, self.__ext) if __name__ == '__main__': import sys + sys.path.insert(0, os.path.abspath('..')) from photo_importer import log diff --git a/photo_importer/importer.py b/photo_importer/importer.py index ddcf802..fc060aa 100755 --- a/photo_importer/importer.py +++ b/photo_importer/importer.py @@ -28,8 +28,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) @@ -48,7 +49,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)) @@ -72,7 +74,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() @@ -88,10 +91,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() diff --git a/photo_importer/mover.py b/photo_importer/mover.py index 03f1dc2..c3af1d7 100755 --- a/photo_importer/mover.py +++ b/photo_importer/mover.py @@ -71,7 +71,9 @@ def __move_file(self, fname, prop): os.path.join( self.__output_path, self.__config['main'][self.OUT_SUBDIR_CFG[prop.type()]], - self.__config['main']['out_date_format'])) + self.__config['main']['out_date_format'], + ) + ) if not os.path.isdir(path): if not self.__dryrun: @@ -120,9 +122,8 @@ def __run(self, args): return True with subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) as proc: + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) as proc: proc.wait() info = proc.stdout.read().strip() if info: @@ -131,8 +132,9 @@ def __run(self, args): if err: logging.error(err.decode("utf-8")) elif proc.returncode != 0: - logging.error('%s failed with code %i' % - (args[0], proc.returncode)) + logging.error( + '%s failed with code %i' % (args[0], proc.returncode) + ) return proc.returncode == 0 def status(self): diff --git a/photo_importer/rotator.py b/photo_importer/rotator.py index ddfd0f5..4ead0f3 100755 --- a/photo_importer/rotator.py +++ b/photo_importer/rotator.py @@ -18,7 +18,7 @@ 5: '-transpose', 6: '-rotate 90', 7: '-transverse', - 8: '-rotate 270' + 8: '-rotate 270', } ORIENTATION_TAG = 'EXIF:Orientation' @@ -47,8 +47,8 @@ def run(self): with concurrent.futures.ThreadPoolExecutor(max_workers=tc) as executor: futures = { - executor.submit(processor, fn): - fn for fn in self.__filenames} + executor.submit(processor, fn): fn for fn in self.__filenames + } for future in concurrent.futures.as_completed(futures): self.__processed += 1 @@ -74,7 +74,8 @@ def __process_exiftran(self, filename): shell=True, universal_newlines=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE).stderr + stderr=subprocess.PIPE, + ).stderr error = '' while True: @@ -102,8 +103,9 @@ def __process_jpegtran(self, filename): if orientation_cmd is None: return True - logging.debug('rotate: jpegtran %s %s' % - (orientation_cmd, filename)) + logging.debug( + 'rotate: jpegtran %s %s' % (orientation_cmd, filename) + ) if self.__dryrun: return True @@ -111,15 +113,19 @@ def __process_jpegtran(self, filename): handle, tmpfile = tempfile.mkstemp(dir=os.path.dirname(filename)) os.close(handle) - cmd = 'jpegtran -copy all -outfile %s %s %s' % \ - (tmpfile, orientation_cmd, filename) + cmd = 'jpegtran -copy all -outfile %s %s %s' % ( + tmpfile, + orientation_cmd, + filename, + ) p = subprocess.Popen( cmd, shell=True, universal_newlines=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE).stderr + stderr=subprocess.PIPE, + ).stderr line = p.readline() if line: @@ -138,15 +144,19 @@ def __process_jpegtran(self, filename): def __get_orientation_cmd(self, fullname): orientation = self.__exiftool.get_tag(ORIENTATION_TAG, fullname) - if orientation is not None and \ - 0 <= orientation and orientation < len(JPEGTRAN_COMMAND): + if ( + orientation is not None + and 0 <= orientation + and orientation < len(JPEGTRAN_COMMAND) + ): return JPEGTRAN_COMMAND[orientation] else: return None def __clear_orientation_tag(self, fullname): - res = self.__exiftool.set_tags( - {ORIENTATION_TAG: 1}, fullname).decode('utf-8') + res = self.__exiftool.set_tags({ORIENTATION_TAG: 1}, fullname).decode( + 'utf-8' + ) if not exiftool.check_ok(res): raise SystemError('exiftool error: ' + exiftool.format_error(res)) try: @@ -159,7 +169,8 @@ def status(self): 'total': len(self.__filenames), 'processed': self.__processed, 'good': self.__good, - 'errors': self.__errors} + 'errors': self.__errors, + } if __name__ == '__main__': diff --git a/photo_importer/run.py b/photo_importer/run.py index 63819f1..0f04fcd 100755 --- a/photo_importer/run.py +++ b/photo_importer/run.py @@ -31,10 +31,15 @@ def __create(self, name, count): self.__pbar = progressbar.ProgressBar( maxval=count, widgets=[ - name, ' ', - progressbar.Percentage(), ' ', - progressbar.Bar(), ' ', - progressbar.ETA()]).start() + name, + ' ', + progressbar.Percentage(), + ' ', + progressbar.Bar(), + ' ', + progressbar.ETA(), + ], + ).start() def run(self): stage = '' @@ -58,8 +63,9 @@ def run(self): self.__pbar.finish() break - if (stage == 'move' or stage == 'rotate') and \ - self.__pbar is not None: + if ( + stage == 'move' or stage == 'rotate' + ) and self.__pbar is not None: self.__pbar.update(stat[stage]['processed']) @@ -80,11 +86,7 @@ def main(): log.initLogger(args.logfile) - imp = importer.Importer( - cfg, - args.in_path, - args.out_path, - args.dryrun) + imp = importer.Importer(cfg, args.in_path, args.out_path, args.dryrun) pbar = ProgressBar(imp) imp.start() diff --git a/photo_importer/server.py b/photo_importer/server.py index b95b14d..6f490f7 100755 --- a/photo_importer/server.py +++ b/photo_importer/server.py @@ -32,7 +32,6 @@ def __str__(self): class PhotoImporterHandler(http.server.BaseHTTPRequestHandler): - def __ok_response(self, result): self.send_response(200) self.send_header('Content-type', 'application/json') @@ -49,11 +48,13 @@ def __error_response(self, code, err): self.send_error(code, explain=str(err)) def __get_mounted_list(self): - return {os.path.basename(dp.device): (dp.device, dp.mountpoint) - for dp in psutil.disk_partitions()} + return { + os.path.basename(dp.device): (dp.device, dp.mountpoint) + for dp in psutil.disk_partitions() + } def __bytes_to_gbytes(self, b): - return round(b / 1024. / 1024. / 1024., 2) + return round(b / 1024.0 / 1024.0 / 1024.0, 2) def __get_removable_devices_posix(self): mount_list = self.__get_mounted_list() @@ -73,13 +74,13 @@ def __get_removable_devices_posix(self): res[pdev] = { 'dev_path': mount_list[pdev][0], 'mount_path': mount_list[pdev][1], - 'read_only': read_only + 'read_only': read_only, } else: res[pdev] = { 'dev_path': '/dev/' + pdev, 'mount_path': '', - 'read_only': read_only + 'read_only': read_only, } return res @@ -96,7 +97,7 @@ def __get_removable_devices_win(self): res[dev_name] = { 'dev_path': dev_name, 'mount_path': d, - 'read_only': not os.access(d, os.W_OK) + 'read_only': not os.access(d, os.W_OK), } return res @@ -113,8 +114,9 @@ def __get_removable_devices(self): res[FIXED_IN_PATH_NAME] = { 'dev_path': FIXED_IN_PATH_NAME, 'mount_path': self.server.fixed_in_path(), - 'read_only': - not os.access(self.server.fixed_in_path(), os.W_OK) + 'read_only': not os.access( + self.server.fixed_in_path(), os.W_OK + ), } return res @@ -138,13 +140,15 @@ def __mount_get_list(self): r['progress'] = 0 r['read_only'] = info['read_only'] r['allow_start'] = not ( - info['read_only'] and self.server.move_mode()) + info['read_only'] and self.server.move_mode() + ) if r['path']: stat = self.server.import_status(r['path']) du = psutil.disk_usage(r['path']) if dev == FIXED_IN_PATH_NAME: r['size'] = self.__bytes_to_gbytes( - self.__folder_size(r['path'])) + self.__folder_size(r['path']) + ) else: r['size'] = self.__bytes_to_gbytes(du.total) r['usage'] = du.percent @@ -152,12 +156,11 @@ def __mount_get_list(self): stage = stat['stage'] r['state'] = stage if stage == 'move' or stage == 'rotate': - r['progress'] = \ - round(100. * - stat[stage]['processed'] / stat['total']) + r['progress'] = round( + 100.0 * stat[stage]['processed'] / stat['total'] + ) elif stage == 'done': - cerr = stat['move']['errors'] + \ - stat['rotate']['errors'] + cerr = stat['move']['errors'] + stat['rotate']['errors'] if cerr != 0: r['state'] = 'error' r['total'] = cerr @@ -178,8 +181,7 @@ def __check_dev_for_mount(self, dev): raise HTTPError(HTTPStatus.BAD_REQUEST, 'empty "d" param') dev_list = self.__get_removable_devices() if dev not in dev_list: - raise HTTPError(HTTPStatus.BAD_REQUEST, - 'wrong device: %s' % dev) + raise HTTPError(HTTPStatus.BAD_REQUEST, 'wrong device: %s' % dev) device = dev_list[dev] if device['mount_path']: self.server.import_done(device['mount_path']) @@ -216,8 +218,9 @@ def __mount_request(self, params): elif action == 'umount': result = self.__mount_umount(dev) else: - raise HTTPError(HTTPStatus.BAD_REQUEST, - 'unknown action %s' % action) + raise HTTPError( + HTTPStatus.BAD_REQUEST, 'unknown action %s' % action + ) self.__ok_response(result) @@ -256,8 +259,9 @@ def __import_request(self, params): result = self.__import_get_log(in_path) self.__text_response(result) else: - raise HTTPError(HTTPStatus.BAD_REQUEST, - 'unknown action %s' % action) + raise HTTPError( + HTTPStatus.BAD_REQUEST, 'unknown action %s' % action + ) def __sysinfo_request(self, params): try: @@ -278,8 +282,7 @@ def __file_request(self, path): try: if (path[0]) == '/': path = path[1:] - fname = os.path.normpath( - os.path.join(self.server.web_path(), path)) + fname = os.path.normpath(os.path.join(self.server.web_path(), path)) if not fname.startswith(self.server.web_path()): logging.warning('incorrect path: ' + path) raise HTTPError(HTTPStatus.NOT_FOUND, path) @@ -387,10 +390,8 @@ def import_start(self, in_path, out_path): logging.info('import_start: %s', in_path) self.__importers[in_path] = importer.Importer( - self.__cfg, - in_path, - out_path, - False) + self.__cfg, in_path, out_path, False + ) self.__importers[in_path].start() From 57b2fbe2e9c8f4e54f2874d5e34250c84dfcb53a Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 00:17:51 +0100 Subject: [PATCH 03/17] Update modules import --- photo_importer/importer.py | 13 +++++-------- photo_importer/server.py | 9 +++------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/photo_importer/importer.py b/photo_importer/importer.py index fc060aa..916cf07 100755 --- a/photo_importer/importer.py +++ b/photo_importer/importer.py @@ -1,17 +1,14 @@ #!/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 config +from . import rotator +from . import fileprop class Importer(threading.Thread): diff --git a/photo_importer/server.py b/photo_importer/server.py index 6f490f7..ebf3424 100755 --- a/photo_importer/server.py +++ b/photo_importer/server.py @@ -2,7 +2,6 @@ import os import re -import sys import glob import json import psutil @@ -12,11 +11,9 @@ import http.server from http import HTTPStatus -sys.path.insert(0, os.path.abspath('..')) - -from photo_importer import log # noqa -from photo_importer import config # noqa -from photo_importer import importer # noqa +from . import log +from . import config +from . import importer FIXED_IN_PATH_NAME = 'none' From b9bbb87646fc8d0e67ac1dbbc1807438a3e011ca Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 29 Nov 2022 00:44:09 +0100 Subject: [PATCH 04/17] Update README.md --- README.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a8a853f..693858a 100644 --- a/README.md +++ b/README.md @@ -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 @@ -18,31 +18,38 @@ 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) + * Debian based Linux (Other Linux versions not officially supported, but might work) or Windows 7 and above ### 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 pmount +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 pip install PyExifTool progressbar psutil +sudo apt install exiftran pmount sudo python3 ./setup.py install ``` @@ -56,9 +63,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 From 3fa90fa913d9801cd69ee46c31429ed8a29b728a Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 09:07:14 +0100 Subject: [PATCH 05/17] Update version/changelog --- debian/changelog | 9 +++++++++ setup.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 8b8d4ca..8265f9d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +photo-importer (1.2.0) stable; urgency=medium + + * Update to new PyExifTool + * Reformat with black + * Update modules import + * Add PyPi install support + + -- Alexander Bushnev Tue, 29 Nov 2022 09:03:24 +0100 + photo-importer (1.1.2) stable; urgency=medium * Update setup scripts diff --git a/setup.py b/setup.py index cf8a023..2d7ee59 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ def get_long_description(): setup( name='photo-importer', - version='1.1.2', + version='1.2.0', description='Photo importer tool', author='Alexander Bushnev', author_email='Alexander@Bushnev.pro', From bd7c062c0a5c27dcf285fec052a6fde995b1a440 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 29 Nov 2022 09:30:16 +0100 Subject: [PATCH 06/17] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 693858a..7a44d7d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Standalone web server for fast media import for headless computer #### Installing via PyPi ```bash -sudo apt install exiftran pmount +sudo apt install exiftran pmount pip sudo pip install photo-importer ``` #### Installing as debian package @@ -48,8 +48,8 @@ sudo dpkg -i ../photo-importer_1.2.0_all.deb ``` #### Installing via setup.py ```bash +sudo apt install exiftran pmount pip sudo pip install PyExifTool progressbar psutil -sudo apt install exiftran pmount sudo python3 ./setup.py install ``` From c39fa75cadfd87eee157b8271cd421c0fb45d0d2 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 29 Nov 2022 09:41:50 +0100 Subject: [PATCH 07/17] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a44d7d..0d7db02 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Standalone web server for fast media import for headless computer #### Installing via PyPi ```bash -sudo apt install exiftran pmount pip +sudo apt install exiftran exiftool pmount pip sudo pip install photo-importer ``` #### Installing as debian package @@ -48,7 +48,7 @@ sudo dpkg -i ../photo-importer_1.2.0_all.deb ``` #### Installing via setup.py ```bash -sudo apt install exiftran pmount pip +sudo apt install exiftran exiftool pmount pip sudo pip install PyExifTool progressbar psutil sudo python3 ./setup.py install ``` From 7ee80652d2020d723dd7cc2c1da808c8dbc441a7 Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 21:31:17 +0100 Subject: [PATCH 08/17] Update rotator to new PyExifTool --- photo_importer/rotator.py | 62 +++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/photo_importer/rotator.py b/photo_importer/rotator.py index 4ead0f3..2327b58 100755 --- a/photo_importer/rotator.py +++ b/photo_importer/rotator.py @@ -7,7 +7,7 @@ import subprocess import concurrent.futures -from photo_importer import config +from . import config JPEGTRAN_COMMAND = { 0: None, @@ -39,8 +39,7 @@ def run(self): processor = self.__process_exiftran self.__exiftool = None if int(self.__config['main']['use_jpegtran']): - self.__exiftool = exiftool.ExifTool() - self.__exiftool.start() + self.__exiftool = exiftool.ExifToolHelper() processor = self.__process_jpegtran tc = 1 @@ -69,25 +68,24 @@ def __process_exiftran(self, filename): if self.__dryrun: return True - p = subprocess.Popen( + error = '' + with subprocess.Popen( cmd, shell=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - ).stderr - - error = '' - while True: - line = p.readline() - if not line: - break - - if line.startswith('processing '): - ok = True - else: - ok = False - error += line + ) as p: + while True: + line = p.stderr.readline() + if not line: + break + + if line.startswith('processing '): + ok = True + else: + ok = False + error += line if error != '': logging.error('exiftran (%s) error: %s' % (filename, error)) @@ -119,18 +117,17 @@ def __process_jpegtran(self, filename): filename, ) - p = subprocess.Popen( + with subprocess.Popen( cmd, shell=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - ).stderr - - line = p.readline() - if line: - logging.error('jpegtran (%s) failed: %s' % (filename, line)) - return False + ) as p: + line = p.stderr.readline() + if line: + logging.error('jpegtran (%s) failed: %s' % (filename, line)) + return False self.__clear_orientation_tag(tmpfile) @@ -143,22 +140,17 @@ def __process_jpegtran(self, filename): return False def __get_orientation_cmd(self, fullname): - orientation = self.__exiftool.get_tag(ORIENTATION_TAG, fullname) - if ( - orientation is not None - and 0 <= orientation - and orientation < len(JPEGTRAN_COMMAND) - ): + tags = self.__exiftool.get_tags(fullname, ORIENTATION_TAG) + if ORIENTATION_TAG not in tags: + return None + orientation = tags[ORIENTATION_TAG] + if 0 <= orientation and orientation < len(JPEGTRAN_COMMAND): return JPEGTRAN_COMMAND[orientation] else: return None def __clear_orientation_tag(self, fullname): - res = self.__exiftool.set_tags({ORIENTATION_TAG: 1}, fullname).decode( - 'utf-8' - ) - if not exiftool.check_ok(res): - raise SystemError('exiftool error: ' + exiftool.format_error(res)) + self.__exiftool.set_tags(fullname, {ORIENTATION_TAG: 1}) try: os.remove(fullname + '_original') except Exception: From 1fcbdbfb0c17800fb22c9fe3a655da4edba6d6ba Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 21:32:04 +0100 Subject: [PATCH 09/17] MacOS support verified --- README.md | 7 ++++++- setup.py | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a44d7d..a2f739b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,12 @@ Standalone web server for fast media import for headless computer ### Requirements: * Python 3.3+ - * Debian based Linux (Other Linux versions not officially supported, but might work) or Windows 7 and above + +### 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](https://pypi.org/project/PyExifTool/) diff --git a/setup.py b/setup.py index 2d7ee59..401b75b 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,7 @@ def get_long_description(): 'Development Status :: 5 - Production/Stable', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', @@ -40,6 +41,7 @@ def get_long_description(): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)' 'Topic :: Scientific/Engineering :: Image Processing', 'Topic :: Multimedia :: Video', 'Topic :: Utilities', From 6a53fd74bdc166bd669f0ee54442121c48917aff Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 21:35:22 +0100 Subject: [PATCH 10/17] Improve fileprop tests --- photo_importer/config.py | 3 +++ photo_importer/fileprop.py | 7 +++++-- photo_importer/fileprop_test.py | 9 +++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/photo_importer/config.py b/photo_importer/config.py index 5c4579b..709499e 100755 --- a/photo_importer/config.py +++ b/photo_importer/config.py @@ -63,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) diff --git a/photo_importer/fileprop.py b/photo_importer/fileprop.py index 43d1370..ae90de9 100755 --- a/photo_importer/fileprop.py +++ b/photo_importer/fileprop.py @@ -17,7 +17,7 @@ class FileProp(object): - DATE_REX = [ + DATE_REGEX = [ ( re.compile('\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}'), '%Y-%m-%d_%H-%M-%S', @@ -74,6 +74,9 @@ def __init__(self, config): self.__out_list = set() self.__exiftool = exiftool.ExifToolHelper() + def __del__(self): + self.__exiftool.terminate() + def __prepare_ext_to_type(self): self.EXT_TO_TYPE = {} for tp, cfg in self.FILE_EXT_CFG.items(): @@ -111,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: diff --git a/photo_importer/fileprop_test.py b/photo_importer/fileprop_test.py index d3b0a70..bdb42b5 100755 --- a/photo_importer/fileprop_test.py +++ b/photo_importer/fileprop_test.py @@ -5,16 +5,17 @@ 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): From b4f73697ba129b1622fd45a0f7dd086a061cb845 Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 21:36:05 +0100 Subject: [PATCH 11/17] Remove legacy modules __main__ --- photo_importer/fileprop_test.py | 4 ---- photo_importer/importer.py | 12 ------------ photo_importer/rotator.py | 9 --------- 3 files changed, 25 deletions(-) diff --git a/photo_importer/fileprop_test.py b/photo_importer/fileprop_test.py index bdb42b5..0f03a92 100755 --- a/photo_importer/fileprop_test.py +++ b/photo_importer/fileprop_test.py @@ -146,7 +146,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() diff --git a/photo_importer/importer.py b/photo_importer/importer.py index 916cf07..0a5883f 100755 --- a/photo_importer/importer.py +++ b/photo_importer/importer.py @@ -114,15 +114,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()) diff --git a/photo_importer/rotator.py b/photo_importer/rotator.py index 2327b58..081feaa 100755 --- a/photo_importer/rotator.py +++ b/photo_importer/rotator.py @@ -163,12 +163,3 @@ def status(self): 'good': self.__good, 'errors': self.__errors, } - - -if __name__ == '__main__': - import sys - - rot = Rotator(config.Config(), sys.argv[1:], False) - rot.run() - - print(rot.status()) From 3e56d92ed563eaa3d6c380410e9cfe934f70517f Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 21:36:39 +0100 Subject: [PATCH 12/17] Add importer test --- photo_importer/importer_test.py | 68 ++++++++++++++++++++++++++++ photo_importer/test_data/img_1.JPG | Bin 0 -> 10723 bytes photo_importer/test_data/img_2.jpeg | Bin 0 -> 13983 bytes 3 files changed, 68 insertions(+) create mode 100755 photo_importer/importer_test.py create mode 100644 photo_importer/test_data/img_1.JPG create mode 100644 photo_importer/test_data/img_2.jpeg diff --git a/photo_importer/importer_test.py b/photo_importer/importer_test.py new file mode 100755 index 0000000..3444ac0 --- /dev/null +++ b/photo_importer/importer_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/python3 + +import os +import sys +import unittest +import tempfile + +from . import config +from . import importer + + +class TestImporter(unittest.TestCase): + def test_importer(self): + with tempfile.TemporaryDirectory() as tmpdirname: + cfg = config.Config() + cfg.set('main', 'move_mode', '0') + imp = importer.Importer( + cfg, + os.path.join(os.path.dirname(__file__), 'test_data'), + tmpdirname, + False, + ) + imp.start() + imp.join() + + self.assertEqual( + imp.status(), + { + 'stage': 'done', + 'total': 2, + 'move': { + 'total': 2, + 'moved': 0, + 'copied': 2, + 'removed': 0, + 'skipped': 0, + 'processed': 2, + 'errors': 0, + }, + 'rotate': { + 'total': 2, + 'processed': 2, + 'good': 2, + 'errors': 0, + }, + }, + ) + + files = [] + for path, cd, fs in os.walk(tmpdirname): + for f in fs: + print(os.path.join(path, f)) + files.append(os.path.join(path, f)) + files.sort() + + self.assertEqual(len(files), 2) + self.assertEqual( + files[0], + os.path.join( + tmpdirname, 'Foto/2021/2021-12-19/2021-12-19_13-11-36.jpeg' + ), + ) + self.assertEqual( + files[1], + os.path.join( + tmpdirname, 'Foto/2022/2022-11-21/2022-11-21_00-42-07.JPG' + ), + ) diff --git a/photo_importer/test_data/img_1.JPG b/photo_importer/test_data/img_1.JPG new file mode 100644 index 0000000000000000000000000000000000000000..c39e50ac7eaa06940dea425579629a2ab90f6c9e GIT binary patch literal 10723 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 QCXTm)W)MFqR+8QOe|ALhhX4Qo literal 0 HcmV?d00001 diff --git a/photo_importer/test_data/img_2.jpeg b/photo_importer/test_data/img_2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5f01fce585ecc960153bb53e7af720fa71aedc4d GIT binary patch literal 13983 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 literal 0 HcmV?d00001 From 1b730285c42c35b305ea7867aa65f7ba76947bfe Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 21:40:29 +0100 Subject: [PATCH 13/17] Update changelog --- debian/changelog | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 8265f9d..4984c38 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,13 @@ photo-importer (1.2.0) stable; urgency=medium + * Add PyPi install support * Update to new PyExifTool * Reformat with black * Update modules import - * Add PyPi install support + * Add importer test + * Improve fileprop tests + * Remove legacy modules __main__ + * MacOS support verified -- Alexander Bushnev Tue, 29 Nov 2022 09:03:24 +0100 From 75dc881dbd05359861851b65b9f5c308209633b0 Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 21:46:54 +0100 Subject: [PATCH 14/17] Cleanup --- photo_importer/fileprop.py | 4 +++- photo_importer/fileprop_test.py | 2 -- photo_importer/importer.py | 1 - photo_importer/importer_test.py | 1 - photo_importer/rotator.py | 5 +++-- photo_importer/server.py | 8 ++++++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/photo_importer/fileprop.py b/photo_importer/fileprop.py index ae90de9..4e3c531 100755 --- a/photo_importer/fileprop.py +++ b/photo_importer/fileprop.py @@ -221,7 +221,9 @@ def out_name_full(self, path=None): if path is None: path = self.__path - return self.__prop_ptr._out_name_full(path, self.__out_name, self.__ext) + return self.__prop_ptr._out_name_full( + path, self.__out_name, self.__ext + ) if __name__ == '__main__': diff --git a/photo_importer/fileprop_test.py b/photo_importer/fileprop_test.py index 0f03a92..9515b44 100755 --- a/photo_importer/fileprop_test.py +++ b/photo_importer/fileprop_test.py @@ -1,7 +1,5 @@ #!/usr/bin/python3 -import os -import sys import unittest import datetime diff --git a/photo_importer/importer.py b/photo_importer/importer.py index 0a5883f..8b6be57 100755 --- a/photo_importer/importer.py +++ b/photo_importer/importer.py @@ -6,7 +6,6 @@ from . import log from . import mover -from . import config from . import rotator from . import fileprop diff --git a/photo_importer/importer_test.py b/photo_importer/importer_test.py index 3444ac0..31346e3 100755 --- a/photo_importer/importer_test.py +++ b/photo_importer/importer_test.py @@ -1,7 +1,6 @@ #!/usr/bin/python3 import os -import sys import unittest import tempfile diff --git a/photo_importer/rotator.py b/photo_importer/rotator.py index 081feaa..ebd46e5 100755 --- a/photo_importer/rotator.py +++ b/photo_importer/rotator.py @@ -7,7 +7,6 @@ import subprocess import concurrent.futures -from . import config JPEGTRAN_COMMAND = { 0: None, @@ -126,7 +125,9 @@ def __process_jpegtran(self, filename): ) as p: line = p.stderr.readline() if line: - logging.error('jpegtran (%s) failed: %s' % (filename, line)) + logging.error( + 'jpegtran (%s) failed: %s' % (filename, line) + ) return False self.__clear_orientation_tag(tmpfile) diff --git a/photo_importer/server.py b/photo_importer/server.py index ebf3424..3a4a337 100755 --- a/photo_importer/server.py +++ b/photo_importer/server.py @@ -157,7 +157,9 @@ def __mount_get_list(self): 100.0 * stat[stage]['processed'] / stat['total'] ) elif stage == 'done': - cerr = stat['move']['errors'] + stat['rotate']['errors'] + cerr = ( + stat['move']['errors'] + stat['rotate']['errors'] + ) if cerr != 0: r['state'] = 'error' r['total'] = cerr @@ -279,7 +281,9 @@ def __file_request(self, path): try: if (path[0]) == '/': path = path[1:] - fname = os.path.normpath(os.path.join(self.server.web_path(), path)) + fname = os.path.normpath( + os.path.join(self.server.web_path(), path) + ) if not fname.startswith(self.server.web_path()): logging.warning('incorrect path: ' + path) raise HTTPError(HTTPStatus.NOT_FOUND, path) From 7b00e6776fb7837e51731b48bf746e963ab92a68 Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 22:12:29 +0100 Subject: [PATCH 15/17] Fix import --- photo_importer/mover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/photo_importer/mover.py b/photo_importer/mover.py index c3af1d7..eb67ac3 100755 --- a/photo_importer/mover.py +++ b/photo_importer/mover.py @@ -5,7 +5,7 @@ import logging import subprocess -from photo_importer import fileprop +from . import fileprop class Mover(object): From ffc3ab9335766dcd0d7e649fbd6a1950a1087b25 Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 22:14:55 +0100 Subject: [PATCH 16/17] Fix import --- photo_importer/run.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/photo_importer/run.py b/photo_importer/run.py index 0f04fcd..2d9cf3d 100755 --- a/photo_importer/run.py +++ b/photo_importer/run.py @@ -1,17 +1,13 @@ #!/usr/bin/python3 -import os -import sys import logging import argparse import threading import progressbar -sys.path.insert(0, os.path.abspath('..')) - -from photo_importer import log # noqa -from photo_importer import config # noqa -from photo_importer import importer # noqa +from . import log +from . import config +from . import importer class ProgressBar(threading.Thread): From d7b48c7c6dd11a825a0f6bd161bec161ac56046a Mon Sep 17 00:00:00 2001 From: Alexander Bushnev Date: Tue, 29 Nov 2022 23:28:34 +0100 Subject: [PATCH 17/17] Formatting --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 401b75b..60a9349 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,9 @@ def get_long_description(): this_directory = os.path.abspath(os.path.dirname(__file__)) - with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f: + with open( + os.path.join(this_directory, 'README.md'), encoding='utf-8' + ) as f: long_description = f.read() return long_description @@ -41,7 +43,7 @@ def get_long_description(): 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)' + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 'Topic :: Scientific/Engineering :: Image Processing', 'Topic :: Multimedia :: Video', 'Topic :: Utilities',