diff --git a/sigal/gallery.py b/sigal/gallery.py index 7e8ac12..8cf7f71 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -40,8 +40,7 @@ from urllib.parse import quote as url_quote from click import get_terminal_size, progressbar from . import image, signals, video -from .image import (get_exif_data, get_exif_tags, get_iptc_data, get_size, - process_image) +from .image import get_exif_tags, get_image_metadata, get_size, process_image from .settings import get_thumb from .utils import (Devnull, cached_property, check_or_create_dir, copy, get_mime, is_valid_html5_video, read_markdown, @@ -84,7 +83,10 @@ class Media: self.thumb_path = join(settings['destination'], path, self.thumb_name) self.logger = logging.getLogger(__name__) + + self.file_metadata = None self._get_metadata() + # default: title is the filename if not self.title: self.title = self.filename @@ -196,32 +198,25 @@ class Image(Media): def _get_metadata(self): super()._get_metadata() + self.file_metadata = get_image_metadata(self.src_path) + # If a title or description hasn't been obtained by other means, look # for the information in IPTC fields if self.title and self.description: # Nothing to do - we already have title and description return - try: - iptc_data = get_iptc_data(self.src_path) - except Exception as e: - self.logger.warning('Could not read IPTC data from %s: %s', - self.src_path, e) - else: - if not self.title and iptc_data.get('title'): - self.title = iptc_data['title'] - if not self.description and iptc_data.get('description'): - self.description = iptc_data['description'] + iptc_data = self.file_metadata['iptc'] + if not self.title and iptc_data.get('title'): + self.title = iptc_data['title'] + if not self.description and iptc_data.get('description'): + self.description = iptc_data['description'] @cached_property def raw_exif(self): """If not `None`, contains the raw EXIF tags.""" - try: - return (get_exif_data(self.src_path) - if self.ext in ('.jpg', '.jpeg') else None) - except Exception as e: - self.logger.warning('Could not read EXIF data from %s: %s', - self.src_path, e) + if self.ext in ('.jpg', '.jpeg'): + return self.file_metadata['exif'] @cached_property def size(self): @@ -460,7 +455,7 @@ class Album: # fallback to the size of src_path if dst_path is missing size = f.size if size is None: - size = get_size(f.src_path) + size = f.file_metadata['size'] if size['width'] > size['height']: self._thumbnail = (url_quote(self.name) + '/' + diff --git a/sigal/image.py b/sigal/image.py index b1a4066..04a0e13 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -56,6 +56,10 @@ def _has_exif_tags(img): def _read_image(file_path): + # The image is already opened + if isinstance(file_path, PILImage.Image): + return file_path + with warnings.catch_warnings(record=True) as caught_warnings: im = PILImage.open(file_path) @@ -203,10 +207,7 @@ def get_size(file_path): logger.error("Could not read size of %s due to %r", file_path, e) else: width, height = im.size - return { - 'width': width, - 'height': height - } + return {'width': width, 'height': height} def get_exif_data(filename): @@ -271,6 +272,34 @@ def get_iptc_data(filename): return iptc_data +def get_image_metadata(filename): + logger = logging.getLogger(__name__) + exif, iptc, size = {}, {}, {} + + try: + img = _read_image(filename) + except Exception as e: + logger.error('Could not open image %s metadata: %s', filename, e) + else: + try: + if os.path.splitext(filename)[1].lower() in ('.jpg', '.jpeg'): + exif = get_exif_data(img) + except Exception as e: + logger.warning('Could not read EXIF data from %s: %s', filename, e) + + try: + iptc = get_iptc_data(img) + except Exception as e: + logger.warning('Could not read IPTC data from %s: %s', filename, e) + + try: + size = get_size(img) + except Exception as e: + logger.warning('Could not read size from %s: %s', filename, e) + + return {'exif': exif, 'iptc': iptc, 'size': size} + + def dms_to_degrees(v): """Convert degree/minute/second to decimal degrees."""