Merge branch 'dev'
This commit is contained in:
		@@ -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
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user