Merge branch 'dev'
This commit is contained in:
commit
22182a5006
@ -8,6 +8,8 @@ from PIL import Image as PilImage
|
|||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import multiprocessing
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
class ApplyImageOrientation:
|
class ApplyImageOrientation:
|
||||||
@ -28,11 +30,51 @@ class ApplyImageOrientation:
|
|||||||
"tiff"
|
"tiff"
|
||||||
]
|
]
|
||||||
|
|
||||||
def run(self, jpeg=False):
|
def run(self, threads_count=None, jpeg=False):
|
||||||
|
|
||||||
images_paths = self.get_input_images()
|
images_paths = self.get_input_images()
|
||||||
|
|
||||||
for image_path in images_paths:
|
lock = threading.RLock()
|
||||||
|
threads = []
|
||||||
|
if threads_count is None:
|
||||||
|
threads_count = multiprocessing.cpu_count()
|
||||||
|
|
||||||
|
print("Applying orientation using %s threads" % (threads_count,))
|
||||||
|
for i in range(threads_count):
|
||||||
|
t = threading.Thread(
|
||||||
|
target=self._apply_orientation_thread,
|
||||||
|
kwargs={
|
||||||
|
"lock": lock,
|
||||||
|
"images_paths": images_paths,
|
||||||
|
"jpeg": jpeg
|
||||||
|
}
|
||||||
|
)
|
||||||
|
threads.append(t)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
with RAIILock(lock):
|
||||||
|
print("Detected keyboard interrupt; Aborting!")
|
||||||
|
images_paths.clear()
|
||||||
|
|
||||||
|
def _apply_orientation_thread(self, lock: threading.RLock, images_paths: list, jpeg: bool):
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
with RAIILock(lock) as raii_lock:
|
||||||
|
|
||||||
|
if len(images_paths) == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
image_path = images_paths.pop()
|
||||||
|
common_path = os.path.commonpath([self.__input_folder, image_path])
|
||||||
|
print("Rotating: %s (%s remaining)" % (image_path[len(common_path) + 1:], len(images_paths)))
|
||||||
|
|
||||||
|
raii_lock.release()
|
||||||
|
|
||||||
self.apply_orientation(
|
self.apply_orientation(
|
||||||
image_path=image_path,
|
image_path=image_path,
|
||||||
jpeg=jpeg
|
jpeg=jpeg
|
||||||
@ -79,13 +121,17 @@ class ApplyImageOrientation:
|
|||||||
rotation_degrees = 0
|
rotation_degrees = 0
|
||||||
if orientation == exif.Orientation.LEFT_BOTTOM:
|
if orientation == exif.Orientation.LEFT_BOTTOM:
|
||||||
rotation_degrees = 90
|
rotation_degrees = 90
|
||||||
|
elif orientation == exif.Orientation.BOTTOM_RIGHT:
|
||||||
|
rotation_degrees = 180
|
||||||
|
elif orientation == exif.Orientation.RIGHT_TOP:
|
||||||
|
rotation_degrees = 270
|
||||||
|
|
||||||
if rotation_degrees != 0:
|
if rotation_degrees != 0:
|
||||||
pil_image_rotated = pil_image.rotate(rotation_degrees, expand=True)
|
pil_image_rotated = pil_image.rotate(rotation_degrees, expand=True)
|
||||||
else:
|
else:
|
||||||
pil_image_rotated = pil_image
|
pil_image_rotated = pil_image
|
||||||
|
|
||||||
print(image_basename, pil_image.size, "==>", orientation, "==>", rotation_degrees)
|
# print(image_basename, pil_image.size, "==>", orientation, "==>", rotation_degrees)
|
||||||
|
|
||||||
image_rotated_file_path = self.make_image_output_path(image_path=image_path, jpeg=jpeg)
|
image_rotated_file_path = self.make_image_output_path(image_path=image_path, jpeg=jpeg)
|
||||||
self.ensure_folder(os.path.dirname(image_rotated_file_path))
|
self.ensure_folder(os.path.dirname(image_rotated_file_path))
|
||||||
@ -108,3 +154,38 @@ class ApplyImageOrientation:
|
|||||||
image_output_path = path_name + ".png"
|
image_output_path = path_name + ".png"
|
||||||
|
|
||||||
return image_output_path
|
return image_output_path
|
||||||
|
|
||||||
|
|
||||||
|
class RAIILock:
|
||||||
|
|
||||||
|
def __init__(self, lock: threading.RLock):
|
||||||
|
|
||||||
|
self.__lock = lock
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
|
||||||
|
self.acquire()
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
|
||||||
|
if self.__lock:
|
||||||
|
|
||||||
|
self.release()
|
||||||
|
self.__lock = None
|
||||||
|
|
||||||
|
def acquire(self):
|
||||||
|
|
||||||
|
if self.__lock:
|
||||||
|
|
||||||
|
self.__lock.acquire()
|
||||||
|
|
||||||
|
def release(self):
|
||||||
|
|
||||||
|
if self.__lock:
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.__lock.release()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
46
README.md
Normal file
46
README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
# Mike's Auto Image Rotator
|
||||||
|
|
||||||
|
This program is quite simple: It will look through your jpeg image's exif data and apply rotation automatically.
|
||||||
|
|
||||||
|
This is useful for preprocessing images before sending them through a second program that may not support orientation via exif data.
|
||||||
|
|
||||||
|
by Mike Peralta
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
* Python3
|
||||||
|
* pipenv
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Enter the repo's directory and install dependencies using pipenv, like so:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ cd /path/to/repo
|
||||||
|
$ pipenv install
|
||||||
|
```
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## Invocation
|
||||||
|
|
||||||
|
Invoke directly like so:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ cd /path/to/repo
|
||||||
|
$ pipenv run python ./main.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Invoke in a persistent environment shell like so:
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
$ cd /path/to/repo
|
||||||
|
$ pipenv shell
|
||||||
|
$ python main.py --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
Use the ***--help*** argument shown above to see all available options.
|
||||||
|
|
18
main.py
18
main.py
@ -33,7 +33,22 @@ def main():
|
|||||||
dest="output_jpeg",
|
dest="output_jpeg",
|
||||||
default=False,
|
default=False,
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Output to jpeg files, not png"
|
help="Output to jpeg files, not png. Should be faster but sacrifices the quality of png."
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--threads",
|
||||||
|
dest="threads_count",
|
||||||
|
default=None,
|
||||||
|
type=int,
|
||||||
|
help="Specify the number of threads to use. Default: Same as the number of detected CPU cores."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--single-thread",
|
||||||
|
dest="threads_count",
|
||||||
|
default=None,
|
||||||
|
action="store_const", const=1,
|
||||||
|
help="Use only one thread."
|
||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -45,6 +60,7 @@ def main():
|
|||||||
output_folder=args.output_folder
|
output_folder=args.output_folder
|
||||||
)
|
)
|
||||||
app.run(
|
app.run(
|
||||||
|
threads_count=args.threads_count,
|
||||||
jpeg=args.output_jpeg
|
jpeg=args.output_jpeg
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user