commit 6b00f62a2c9ad692528f096a7f3bcd8f992fc238 Author: Mike Date: Mon Jan 4 12:00:05 2021 -0800 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90f3080 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ + + +# +**/.idea/ +**/__pycache__/ + + diff --git a/ApplyImageOrientation.py b/ApplyImageOrientation.py new file mode 100644 index 0000000..9d252c2 --- /dev/null +++ b/ApplyImageOrientation.py @@ -0,0 +1,110 @@ + + +import exif +from exif import Image as ExifImage + + +from PIL import Image as PilImage + + +import os + + +class ApplyImageOrientation: + + def __init__(self, input_folder, output_folder): + + self.ensure_folder(output_folder) + + assert os.path.isdir(input_folder), "Invalid input folder: %s" % input_folder + assert os.path.isdir(output_folder), "Invalid output folder: %s" % output_folder + + self.__input_folder = os.path.abspath(input_folder) + self.__output_folder = os.path.abspath(output_folder) + + self.__valid_image_extensions = [ + "jpeg", "jpg", + "png", + "tiff" + ] + + def run(self, jpeg=False): + + images_paths = self.get_input_images() + + for image_path in images_paths: + self.apply_orientation( + image_path=image_path, + jpeg=jpeg + ) + + @staticmethod + def ensure_folder(folder_path): + + os.makedirs(folder_path, exist_ok=True) + + def get_input_images(self): + + return self.get_directory_images(self.__input_folder) + + def get_directory_images(self, path): + + images_files_paths = [] + + for current_dir, subdirs, files in os.walk(path): + + for file_name in files: + + file_path = os.path.join(current_dir, file_name) + file_basename, file_extension = os.path.splitext(file_path) + if file_extension: + file_extension = file_extension[1:] + file_extension = file_extension.lower() + if file_extension in self.__valid_image_extensions: + images_files_paths.append(file_path) + + return images_files_paths + + def apply_orientation(self, image_path, jpeg=False): + + image_basename = os.path.basename(image_path) + + with open(image_path, "rb") as f: + + exif_image = ExifImage(f) + orientation = exif_image.orientation + + pil_image = PilImage.open(image_path) + + rotation_degrees = 0 + if orientation == exif.Orientation.LEFT_BOTTOM: + rotation_degrees = 90 + + if rotation_degrees != 0: + pil_image_rotated = pil_image.rotate(rotation_degrees, expand=True) + else: + pil_image_rotated = pil_image + + print(image_basename, pil_image.size, "==>", orientation, "==>", rotation_degrees) + + 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)) + pil_image_rotated.save(image_rotated_file_path, None if jpeg else "png") + + def get_image_relative_path(self, image_path): + + image_path_relative = image_path[len(self.__input_folder):] + + return image_path_relative + + def make_image_output_path(self, image_path, jpeg=False): + + image_path_relative = self.get_image_relative_path(image_path=image_path) + + image_output_path = self.__output_folder + image_path_relative + + if not jpeg: + path_name, extension = os.path.splitext(image_output_path) + image_output_path = path_name + ".png" + + return image_output_path diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..de789b2 --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +exif = "*" +pillow = "*" + +[dev-packages] + +[requires] +python_version = "3" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..52fdf9a --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,69 @@ +{ + "_meta": { + "hash": { + "sha256": "1900f06584008e6cfdf5a311b77e1a8370d743e22ffdead6bbf0ce186dad4e9f" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "exif": { + "hashes": [ + "sha256:87699a3032b902dc72b6e565c51ce2711390cb6d5a7689ff12e04ba427dc95c2", + "sha256:dc53ffc2e09245676944367b0b7038d6cb7d73175d80b4ec9d84333d1af17500" + ], + "index": "pypi", + "version": "==1.0.4" + }, + "pillow": { + "hashes": [ + "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6", + "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865", + "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174", + "sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032", + "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a", + "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e", + "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378", + "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17", + "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c", + "sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913", + "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7", + "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0", + "sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820", + "sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba", + "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2", + "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b", + "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9", + "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234", + "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d", + "sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5", + "sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206", + "sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9", + "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8", + "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59", + "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d", + "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a", + "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b", + "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d" + ], + "index": "pypi", + "version": "==8.1.0" + }, + "plum-py": { + "hashes": [ + "sha256:9328ec7502046134c5f16b04d85947559006e200d7268b7d5ed30f88735a48e5" + ], + "version": "==0.3.1" + } + }, + "develop": {} +} diff --git a/main.py b/main.py new file mode 100755 index 0000000..06fa7da --- /dev/null +++ b/main.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + + +from ApplyImageOrientation import ApplyImageOrientation + +import argparse + + +def main(): + + parser = argparse.ArgumentParser( + prog="Mike's Auto Image Orientation Applier" + ) + + parser.add_argument( + "--input", "--dir", "--folder", + dest="input_folder", + required=True, + type=str, + help="Specify an input folder of images" + ) + + parser.add_argument( + "--output", "--output-dir", "--output-folder", + dest="output_folder", + required=True, + type=str, + help="Specify an output folder for the images" + ) + + parser.add_argument( + "--jpg", "--jpeg", + dest="output_jpeg", + default=False, + action="store_true", + help="Output to jpeg files, not png" + ) + + args = parser.parse_args() + + assert args.input_folder != args.output_folder, "Input and Output folders must be different" + + app = ApplyImageOrientation( + input_folder=args.input_folder, + output_folder=args.output_folder + ) + app.run( + jpeg=args.output_jpeg + ) + + +if __name__ == "__main__": + main()