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