diff --git a/.idea/blur-exif-face-tags.iml b/.idea/blur-exif-face-tags.iml index 6807b60..76cf61c 100644 --- a/.idea/blur-exif-face-tags.iml +++ b/.idea/blur-exif-face-tags.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 127b305..1f0a481 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/README.md b/README.md index f10d1f1..77db2c8 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,6 @@ Many thanks to -* https://www.thregr.org/~wavexx/software/facedetect/#blurring-faces-within-an-image -* "Make blur all around a rectangle in image with PIL", https://stackoverflow.com/q/56987112/6334421 -* Example image: https://unsplash.com/photos/1qfy-jDc_jo?utm_source=unsplash&utm_medium=referral&utm_content=creditShareLink +* [https://www.thregr.org/~wavexx/software/facedetect/#blurring-faces-within-an-image]() +* "Make blur all around a rectangle in image with PIL", [https://stackoverflow.com/q/56987112/6334421]() +* Example image: [https://unsplash.com/photos/1qfy-jDc_jo?utm_source=unsplash&utm_medium=referral&utm_content=creditShareLink]() diff --git a/blur.py b/blur.py index 7ae8916..75aeb96 100644 --- a/blur.py +++ b/blur.py @@ -35,23 +35,24 @@ class NormalizedRectangle: raise Exception -def blur_rectangle0(image_src: exif.Image, region: exif.ExifImageRegion, image_dst: Path = None): +def blur_rectangle0(image_src: exif.Image, region: exif.ExifImageRegion, image_dst: Path = None) -> Path: if region.area_unit == 'normalized': - blur_rectangle1(image_src, normalized_rectangles=[NormalizedRectangle.of_exif_image_region(region=region)], - image_dst=image_dst) + return blur_rectangle1(image_src, + normalized_rectangles=[NormalizedRectangle.of_exif_image_region(region=region)], + image_dst=image_dst) else: - raise Exception + raise Exception(f'Unknown area_unit: {region.area_unit}') -def blur_rectangle1(image_src: exif.Image, normalized_rectangles: List[NormalizedRectangle], image_dst: Path = None): - blur_rectangle2(image_src.get_image_file(), normalized_rectangles, - image_dst=image_dst) +def blur_rectangle1(image_src: exif.Image, normalized_rectangles: List[NormalizedRectangle], image_dst: Path = None) -> Path: + return blur_rectangle2(image_src.get_image_file(), + normalized_rectangles, + image_dst=image_dst) -def blur_rectangle2(image_src: Path, normalized_rectangles: List[NormalizedRectangle], image_dst: Path = None): +def blur_rectangle2(image_src: Path, normalized_rectangles: List[NormalizedRectangle], image_dst: Path = None) -> Path: if len(normalized_rectangles) == 0: - print('No rectangles to blur') - return + raise Exception('No rectangles to blur') # Open an image im = Image.open(image_src) @@ -85,6 +86,8 @@ def blur_rectangle2(image_src: Path, normalized_rectangles: List[NormalizedRecta image_dst = get_image_dst(image_src) im.save(image_dst) + return image_dst + def get_image_dst(image: Path): return image.parent.joinpath(f'{image.stem}{stem_suffix()}{image.suffix}') diff --git a/exec.py b/exec.py index 497acf9..51ea021 100644 --- a/exec.py +++ b/exec.py @@ -2,12 +2,13 @@ from typing import List import sys import subprocess + def execute_save(command: List[str]): returncode, stdout, stderr = execute(command) if returncode == 0: return stdout else: - raise Exception + raise Exception(f'Non-zero returncode: {returncode}\n{stderr}') def execute(command: List[str]): diff --git a/exif.py b/exif.py index bebb52d..95c2476 100644 --- a/exif.py +++ b/exif.py @@ -86,6 +86,10 @@ def get_exif_image_regions(image: Image) -> List[ExifImageRegion]: area_units_str = exec.execute_save(['exiftool', '-RegionAreaUnit', str(img_metadata_file)]) rectangles_str = exec.execute_save(['exiftool', '-RegionRectangle', str(img_metadata_file)]) + if len(names_str) == len(r_types_str) == len(area_units_str) == len(rectangles_str) == 0: + # there are no tagged areas on this image + return [] + names = names_str.strip().split(':', 1)[1].strip().split(', ') r_types = r_types_str.strip().split(':', 1)[1].strip().split(', ') area_units = area_units_str.strip().split(':', 1)[1].strip().split(', ') diff --git a/main.py b/main.py index 3cc278d..5a10fef 100644 --- a/main.py +++ b/main.py @@ -1,47 +1,78 @@ import os from pathlib import Path -from typing import List - -import exif, blur +from typing import List, Union +import blur +import exec +import exif from exif import ExifImageRegion, get_exif_image_regions +# ======================================================================================================= # + +# Adjust path to folder with images +image_directory = Path('example') + +# Enter name of persons to be blurred. Leave empty to blur any face. +names = ['A', 'B', 'C'] +# names = [] + +delete_original: bool = True # deletes the original image after blurring image was created +copy_metadata_gps: bool = True # copies gps location from original to blurred image +copy_metadata_timestamp: bool = True # TODO: copies timestamp from original to blurred image + +# lower-case image extensions image_extensions = ['.jpg', '.jpeg', '.png'] -image_directory = Path('example/') # TODO: Adjust path to folder with images -r_type = 'Face' -names = ['A', 'B', 'C'] # TODO: Enter name of persons to be blurred +r_types = ['Face'] + +# ======================================================================================================= # -def blur_image(image: exif.Image): +def blur_image(image: exif.Image) -> Union[Path, None]: + """ + If at least one tagged area of the image matches the criteria, + a blurred image is created and it's path returned. + """ exif_image_regions: List[ExifImageRegion] = get_exif_image_regions(image=image) - # Blur all tagged areas - # normalized_rectangles = [blur.NormalizedRectangle.of_exif_image_region(region) for region in exif_image_regions] - # Blur only some faces normalized_rectangles = [] for region in exif_image_regions: - if region.r_type == r_type and region.name in names: + if len(r_types) > 0 and region.r_type not in r_types: + continue + if region.name in names or len(names) == 0: normalized_rectangles += [blur.NormalizedRectangle.of_exif_image_region(region)] - print(f'{image} contains {len(normalized_rectangles)} tagged faces to be blurred!') - blur.blur_rectangle1(image_src=image, normalized_rectangles=normalized_rectangles) + if len(normalized_rectangles) > 0: + print(f' Blurring {len(normalized_rectangles)} areas ...') + return blur.blur_rectangle1(image_src=image, normalized_rectangles=normalized_rectangles) + else: + return None def main(): - # Convert all images in the image_directory, including subdirectories. + # Convert all images in `image_directory`, including subdirectories. for _, _, files in os.walk(image_directory): for relative_file_str in files: file: Path = Path.joinpath(image_directory, relative_file_str) - if file.suffix.lower() in image_extensions: + if file.suffix.lower() not in image_extensions: + continue + if file.stem.endswith(blur.stem_suffix()): + print(f'Skipped the following image as it is already blurred:\n\t{file}') + continue + if blur.get_image_dst(file).exists(): + print(f'Skipped the following image as it\'s blurred output does already exist:\n\t{file}') + continue - if file.stem.endswith(blur.stem_suffix()): - print(f'Skipped the following image as it is already blurred:\n\t{file}') - elif blur.get_image_dst(file).exists(): - print(f'Skipped the following image as it\'s blurred output does already exist:\n\t{file}') - else: - blur_image(exif.Image(file)) + print(f'{file}') + blurred_img = blur_image(exif.Image(file)) + + if blurred_img is not None and copy_metadata_gps: + print(f' Copying gps metadata to blurred file ...') + exec.execute_save(['exiftool', '-tagsfromfile', str(file), '-gps:all', str(blurred_img)]) + if blurred_img is not None and delete_original: + print(f' Deleting original file ...') + file.unlink() if __name__ == '__main__':