from PIL import Image, ImageOps, ImageEnhance, ExifTags
|
|
from dominate.tags import *
|
|
import dominate
|
|
import glob as g, os
|
|
import json
|
|
import traceback
|
|
|
|
class GalleryHTML:
|
|
"""A maker of thumbnails, HTML galleries, and simple JSON indices"""
|
|
|
|
def __init__(self):
|
|
# Set some defaults:
|
|
self.output_dir = '.'
|
|
self.base_url = ""
|
|
self.extensions = ['JPG', 'jpg', 'jpeg', 'png', 'gif', 'bmp']
|
|
self.image_list = []
|
|
self.x = 200
|
|
self.y = 200
|
|
self.square = False
|
|
self.overwrite = False
|
|
|
|
def add_images_from(self, source):
|
|
# Ensure that thumbnail directory exists:
|
|
thumb_directory = self.output_dir + "/Thumbs"
|
|
os.makedirs(thumb_directory, exist_ok=True)
|
|
|
|
for image in sorted(self._get_images(source)):
|
|
image_basename = os.path.basename(image)
|
|
thumbnail_src = self.base_url + 'Thumbs/' + image_basename
|
|
image_href = self.base_url + image_basename
|
|
|
|
image_size = self._thumbnailize(image, thumb_directory + '/' + image_basename)
|
|
self.image_list.append((thumbnail_src, image_href, image_size))
|
|
|
|
def _get_images(self, source):
|
|
"""Get a list of images from a source directory or pattern."""
|
|
images = []
|
|
|
|
if os.path.isdir(source):
|
|
# Grab things with common file extensions out of dirs:
|
|
for extension in self.extensions:
|
|
images.extend(g.glob(source + '/*.' + extension))
|
|
else:
|
|
# Assume a pattern or single filename:
|
|
images.extend(g.glob(source))
|
|
|
|
return images
|
|
|
|
def images_html(self):
|
|
"""Return HTML for an image list."""
|
|
html_output = ''
|
|
for display_image in self.image_list:
|
|
(x, y) = display_image[2]
|
|
html_output += a(img(src=display_image[0], width=x, height=y),
|
|
href=display_image[1]).render() + "\n"
|
|
return html_output.strip()
|
|
|
|
def images_json(self):
|
|
"""Return JSON for an image list."""
|
|
return json.dumps(self.image_list)
|
|
|
|
def _handle_exif(self, source_image):
|
|
"""
|
|
Handle EXIF rotation data, maybe - per:
|
|
https://stackoverflow.com/questions/4228530/pil-thumbnail-is-rotating-my-image
|
|
"""
|
|
|
|
# PNG files don't have EXIF data; avoid an exception on _getexif():
|
|
if source_image.format == 'PNG':
|
|
return source_image
|
|
|
|
try:
|
|
for tag in ExifTags.TAGS.keys():
|
|
if ExifTags.TAGS[tag] == 'Orientation':
|
|
break
|
|
|
|
if source_image._getexif():
|
|
# We have EXIF metadata to work with:
|
|
exif = dict(source_image._getexif().items())
|
|
|
|
orientation = exif.get(tag, False)
|
|
|
|
if orientation == 3:
|
|
source_image = source_image.rotate(180, expand=True)
|
|
elif orientation == 6:
|
|
source_image = source_image.rotate(270, expand=True)
|
|
elif orientation == 8:
|
|
source_image = source_image.rotate(90, expand=True)
|
|
except:
|
|
traceback.print_exc()
|
|
|
|
return source_image;
|
|
|
|
def _thumbnailize(self, image, thumbnail_file):
|
|
"""Write thumbnails."""
|
|
image_size = (self.x, self.y)
|
|
|
|
if os.path.isfile(thumbnail_file) and (not self.overwrite):
|
|
# Skip existing files, unless we've been told to overwrite, but make
|
|
# sure we know the correct width and height for them:
|
|
with open(thumbnail_file, 'r+b') as f:
|
|
with Image.open(f) as existing_thumbnail:
|
|
image_size = existing_thumbnail.size
|
|
else:
|
|
# Write a new thumbnail:
|
|
with open(image, 'r+b') as f:
|
|
with Image.open(f) as source_image:
|
|
source_image = self._handle_exif(source_image);
|
|
# thumb = ImageEnhance.Sharpness(ImageOps.fit(source_image, (thumb_x, thumb_y))).enhance(0.15)
|
|
if self.square:
|
|
# Square things off, then make and save thumbnail
|
|
smaller_dim = min(source_image.size)
|
|
thumb = ImageOps.fit(source_image, (smaller_dim, smaller_dim))
|
|
else:
|
|
thumb = source_image
|
|
|
|
thumb.thumbnail((self.x, self.y))
|
|
image_size = thumb.size
|
|
thumb.save(thumbnail_file, source_image.format)
|
|
|
|
return image_size
|