mirror of
https://codeberg.org/privacy1st/blur-exif-face-tags
synced 2024-12-23 01:16:04 +01:00
+ 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:
parent
ce307c09d2
commit
8a4b8aeea4
2
.idea/blur-exif-face-tags.iml
generated
2
.idea/blur-exif-face-tags.iml
generated
@ -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
2
.idea/misc.xml
generated
@ -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>
|
||||
|
@ -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
23
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}')
|
||||
|
3
exec.py
3
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]):
|
||||
|
4
exif.py
4
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(', ')
|
||||
|
73
main.py
73
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__':
|
||||
|
Loading…
Reference in New Issue
Block a user