+ if no names given, convert any face tag

+ if no tag-type given, convert any tagged area
+ option to delete input file
+ option to copy gps metadata
This commit is contained in:
Daniel Langbein 2022-03-09 15:28:11 +01:00
parent ce307c09d2
commit 8a4b8aeea4
7 changed files with 76 additions and 37 deletions

View File

@ -2,7 +2,7 @@
<module version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.9 (venv39-2)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.10 (venv-310)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

2
.idea/misc.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (venv39-2)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (venv-310)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>

View File

@ -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]()

23
blur.py
View File

@ -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}')

View File

@ -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]):

View File

@ -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(', ')

73
main.py
View File

@ -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__':