feat: ADD optional exif data removable in files #18
This commit is contained in:
parent
5b6edac236
commit
7a8ecafc3a
4 changed files with 99 additions and 3 deletions
78
picopaper.py
78
picopaper.py
|
|
@ -12,6 +12,16 @@ from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_SECTIONS_FROM_M
|
|||
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS,
|
||||
BLOGROLL_PATH, ROOT_PAGE)
|
||||
|
||||
# STRIP_IMAGE_EXIF is a newer setting; older config.py files may not define it.
|
||||
# Default to enabled so the privacy feature works without a config update.
|
||||
try:
|
||||
from config import STRIP_IMAGE_EXIF
|
||||
except ImportError:
|
||||
STRIP_IMAGE_EXIF = True
|
||||
|
||||
# Raster image formats whose metadata (EXIF/GPS/XMP/text chunks) is stripped on copy
|
||||
STRIP_IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.tiff', '.tif', '.bmp'}
|
||||
|
||||
class SSGGGenerator:
|
||||
def __init__(self, items_dir='items', output_dir='output', theme=None, blog_title=None, blog_description=None):
|
||||
self.items_dir = Path(items_dir)
|
||||
|
|
@ -29,6 +39,7 @@ class SSGGGenerator:
|
|||
self.hide_logo = HIDE_LOGO
|
||||
self.hide_title = HIDE_TITLE
|
||||
self.logo_path = LOGO_PATH
|
||||
self.strip_image_exif = STRIP_IMAGE_EXIF
|
||||
|
||||
# Setup Jinja2
|
||||
self.env = Environment(loader=FileSystemLoader(self.templates_dir))
|
||||
|
|
@ -383,6 +394,38 @@ class SSGGGenerator:
|
|||
|
||||
print(f"✓ Generated {output_path}")
|
||||
|
||||
def strip_image_metadata(self, src_path, dest_path):
|
||||
"""Re-encode an image to dest_path without any metadata (EXIF/GPS/XMP/text).
|
||||
|
||||
Pillow does not carry metadata across a save unless it is passed
|
||||
explicitly, so re-saving produces a clean image. Returns True on
|
||||
success, or False if the image should be copied verbatim instead
|
||||
(unsupported/animated) or re-encoding failed.
|
||||
"""
|
||||
from PIL import Image
|
||||
|
||||
try:
|
||||
with Image.open(src_path) as img:
|
||||
img.load()
|
||||
|
||||
# Animated images (GIF/WebP/APNG) are copied as-is to avoid
|
||||
# dropping frames; they don't carry camera EXIF/GPS anyway.
|
||||
if getattr(img, 'is_animated', False):
|
||||
return False
|
||||
|
||||
save_kwargs = {}
|
||||
if img.format == 'JPEG':
|
||||
# 'keep' reuses the original quantization tables, so there
|
||||
# is no generational quality loss from re-encoding.
|
||||
save_kwargs['quality'] = 'keep'
|
||||
|
||||
# Saving without exif=/pnginfo= drops all metadata.
|
||||
img.save(dest_path, format=img.format, **save_kwargs)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ⚠ Could not strip metadata from {src_path.name} ({e}); copying as-is")
|
||||
return False
|
||||
|
||||
def copy_assets(self):
|
||||
"""Copy theme assets and images to output directory"""
|
||||
import shutil
|
||||
|
|
@ -395,14 +438,43 @@ class SSGGGenerator:
|
|||
shutil.copytree(self.assets_dir, dest_dir)
|
||||
print(f"✓ Copied theme assets to output")
|
||||
|
||||
# Copy images
|
||||
# Copy images (stripping EXIF/metadata when enabled)
|
||||
images_dir = Path('images')
|
||||
if images_dir.exists():
|
||||
dest_dir = self.output_dir / 'images'
|
||||
if dest_dir.exists():
|
||||
shutil.rmtree(dest_dir)
|
||||
shutil.copytree(images_dir, dest_dir)
|
||||
print(f"✓ Copied images/ to output")
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Only strip if enabled and Pillow is importable; otherwise copy as-is.
|
||||
pillow_available = self.strip_image_exif
|
||||
if self.strip_image_exif:
|
||||
try:
|
||||
import PIL # noqa: F401
|
||||
except ImportError:
|
||||
pillow_available = False
|
||||
print(" ⚠ STRIP_IMAGE_EXIF is enabled but Pillow is not installed; "
|
||||
"copying images without stripping (pip install pillow)")
|
||||
|
||||
stripped = 0
|
||||
for item in images_dir.rglob('*'):
|
||||
if not item.is_file():
|
||||
continue
|
||||
rel_path = item.relative_to(images_dir)
|
||||
dest_path = dest_dir / rel_path
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if (pillow_available
|
||||
and item.suffix.lower() in STRIP_IMAGE_EXTENSIONS
|
||||
and self.strip_image_metadata(item, dest_path)):
|
||||
stripped += 1
|
||||
else:
|
||||
shutil.copy2(item, dest_path)
|
||||
|
||||
if pillow_available:
|
||||
print(f"✓ Copied images/ to output ({stripped} stripped of EXIF/metadata)")
|
||||
else:
|
||||
print(f"✓ Copied images/ to output")
|
||||
|
||||
# Copy static files (GPG keys, .well-known, etc.)
|
||||
static_dir = Path('static')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue