From 20daa3916c0e86a827b80d0e8eb28d63f1922652 Mon Sep 17 00:00:00 2001 From: franek Date: Fri, 12 Jun 2015 22:21:49 +0200 Subject: [PATCH 1/5] add size property into Image object containing width and height of each image (thumbnail, big). Can be useful for some themes to disinguish orientation of the image. --- sigal/gallery.py | 12 ++++++++++-- sigal/image.py | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/sigal/gallery.py b/sigal/gallery.py index 62e2689..e0c1cbe 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -41,7 +41,7 @@ from PIL import Image as PILImage from . import image, video, signals from .compat import PY2, UnicodeMixin, strxfrm, url_quote, text_type -from .image import process_image, get_exif_tags, get_exif_data +from .image import process_image, get_exif_tags, get_exif_data, get_size from .settings import get_thumb from .utils import (Devnull, copy, check_or_create_dir, url_from_path, read_markdown, cached_property, is_valid_html5_video, @@ -168,6 +168,14 @@ class Image(Media): self.src_path) return None + @cached_property + def size(self): + try: + return get_size(self) + except (IOError, IndexError, TypeError, AttributeError): + self.logger.warning(u'Could not read size %s', + self.src_path) + return None class Video(Media): """Gather all informations on a video file.""" @@ -371,7 +379,7 @@ class Album(UnicodeMixin): except: self.logger.error("Failed to open %s", f.src_path) else: - if im.size[0] > im.size[1]: + if im.size[0] > im.size[1]: self._thumbnail = join(self.name, f.thumbnail) self.logger.debug( "Use 1st landscape image as thumbnail for %r :" diff --git a/sigal/image.py b/sigal/image.py index 0450ded..f0b40f4 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -160,6 +160,18 @@ def process_image(filepath, outpath, settings): return Status.SUCCESS +def get_size(filename): + """Return image size.""" + big = PILImage.open(filename.dst_path) + widthBig,heightBig = big.size + thumb = PILImage.open(filename.thumb_path) + widthThumb,heightThumb = thumb.size + data = { + 'big' : {'width': widthBig, 'height': heightBig}, + 'thumbnail': {'width': widthThumb, 'height': heightThumb}, + } + return data + def get_exif_data(filename): """Return a dict with the raw EXIF data.""" From a7e9c174891b0412794b1e7be27195e479aabaa1 Mon Sep 17 00:00:00 2001 From: franek Date: Sun, 14 Jun 2015 12:54:19 +0200 Subject: [PATCH 2/5] Refactor get_size method and add 2 properties to Image (size and thumb_size). Add tests for get_size method --- AUTHORS | 1 + sigal/gallery.py | 14 ++++++++++++-- sigal/image.py | 25 ++++++++++++++----------- tests/test_image.py | 12 +++++++++++- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/AUTHORS b/AUTHORS index 30d99e0..eca2848 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,7 @@ alphabetical order): - Andreas Sieferlinger - Antoine Pitrou - Christophe-Marie Duquesne +- @franek (François D.) - Giel van Schijndel - Jamie Starke - @jdn06 diff --git a/sigal/gallery.py b/sigal/gallery.py index e0c1cbe..8508219 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -3,6 +3,7 @@ # Copyright (c) 2009-2014 - Simon Conseil # Copyright (c) 2013 - Christophe-Marie Duquesne # Copyright (c) 2014 - Jonas Kaufmann +# Copyright (c) 2015 - François D. # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to @@ -171,12 +172,21 @@ class Image(Media): @cached_property def size(self): try: - return get_size(self) + return get_size(self.dst_path) except (IOError, IndexError, TypeError, AttributeError): self.logger.warning(u'Could not read size %s', - self.src_path) + self.dst_path) return None + @cached_property + def thumb_size(self): + try: + return get_size(self.thumb_path) + except (IOError, IndexError, TypeError, AttributeError): + self.logger.warning(u'Could not read size %s', + self.thumb_path) + return None + class Video(Media): """Gather all informations on a video file.""" diff --git a/sigal/image.py b/sigal/image.py index f0b40f4..5075812 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -1,6 +1,7 @@ # -*- coding:utf-8 -*- # Copyright (c) 2009-2014 - Simon Conseil +# Copyright (c) 2015 - François D. # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to @@ -160,17 +161,19 @@ def process_image(filepath, outpath, settings): return Status.SUCCESS -def get_size(filename): - """Return image size.""" - big = PILImage.open(filename.dst_path) - widthBig,heightBig = big.size - thumb = PILImage.open(filename.thumb_path) - widthThumb,heightThumb = thumb.size - data = { - 'big' : {'width': widthBig, 'height': heightBig}, - 'thumbnail': {'width': widthThumb, 'height': heightThumb}, - } - return data +def get_size(file_path): + """Return image size (width and height).""" + logger = logging.getLogger(__name__) + try: + im = PILImage.open(file_path) + except: + logger.error("Failed to open %s", file_path) + else: + width,height = im.size + return { + 'width': width, + 'height': height + } def get_exif_data(filename): """Return a dict with the raw EXIF data.""" diff --git a/tests/test_image.py b/tests/test_image.py index b6c27b2..1edd9aa 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -6,7 +6,7 @@ from PIL import Image from sigal import init_logging from sigal.image import (generate_image, generate_thumbnail, get_exif_tags, - get_exif_data) + get_exif_data, get_size) from sigal.settings import create_settings CURRENT_DIR = os.path.dirname(__file__) @@ -115,3 +115,13 @@ def test_exif_gps(tmpdir): assert abs(simple['gps']['lat'] - lat) < 0.0001 assert abs(simple['gps']['lon'] - lon) < 0.0001 + +def test_get_size(tmpdir): + """Test reading out image size""" + + test_image = 'flickr_jerquiaga_2394751088_cc-by-nc.jpg' + src_file = os.path.join(CURRENT_DIR, 'sample', 'pictures', 'dir1', 'test1', + test_image) + + result = get_size(src_file) + assert result == {'height': 800, 'width': 600} \ No newline at end of file From 78b23090feed847db44d7c921ff49c02cc948878 Mon Sep 17 00:00:00 2001 From: franek Date: Mon, 15 Jun 2015 11:10:50 +0200 Subject: [PATCH 3/5] add test on get_size method when missing image --- tests/test_image.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_image.py b/tests/test_image.py index 1edd9aa..638ac26 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -124,4 +124,13 @@ def test_get_size(tmpdir): test_image) result = get_size(src_file) - assert result == {'height': 800, 'width': 600} \ No newline at end of file + assert result == {'height': 800, 'width': 600} + +def test_get_size_with_invalid_path(tmpdir): + """Test reading out image size with a missing file""" + + test_image = 'missing-file.jpg' + src_file = os.path.join(CURRENT_DIR, test_image) + + result = get_size(src_file) + assert result == None \ No newline at end of file From c84a6219849d4f235d6ab4ff48165fa167fb6e2d Mon Sep 17 00:00:00 2001 From: franek Date: Mon, 15 Jun 2015 11:12:22 +0200 Subject: [PATCH 4/5] refactor exceptions in get_size method --- sigal/gallery.py | 14 ++------------ sigal/image.py | 4 ++-- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/sigal/gallery.py b/sigal/gallery.py index 8508219..f21ebe7 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -171,21 +171,11 @@ class Image(Media): @cached_property def size(self): - try: - return get_size(self.dst_path) - except (IOError, IndexError, TypeError, AttributeError): - self.logger.warning(u'Could not read size %s', - self.dst_path) - return None + return get_size(self.dst_path) @cached_property def thumb_size(self): - try: - return get_size(self.thumb_path) - except (IOError, IndexError, TypeError, AttributeError): - self.logger.warning(u'Could not read size %s', - self.thumb_path) - return None + return get_size(self.thumb_path) class Video(Media): """Gather all informations on a video file.""" diff --git a/sigal/image.py b/sigal/image.py index 5075812..f91996d 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -166,8 +166,8 @@ def get_size(file_path): logger = logging.getLogger(__name__) try: im = PILImage.open(file_path) - except: - logger.error("Failed to open %s", file_path) + except (IOError, IndexError, TypeError, AttributeError) as e : + logger.error("Could not read size of %s due to %r", file_path, e) else: width,height = im.size return { From 07ac771881411a9009d384d14c072ece8e455ecd Mon Sep 17 00:00:00 2001 From: Simon Conseil Date: Mon, 22 Jun 2015 23:52:28 +0200 Subject: [PATCH 5/5] Use the new size attribute + pep8 cleanup. --- sigal/gallery.py | 24 +++++++++++++----------- sigal/image.py | 13 +++++++------ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/sigal/gallery.py b/sigal/gallery.py index f21ebe7..9c234da 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -177,6 +177,7 @@ class Image(Media): def thumb_size(self): return get_size(self.thumb_path) + class Video(Media): """Gather all informations on a video file.""" @@ -374,17 +375,18 @@ class Album(UnicodeMixin): for f in self.medias: ext = splitext(f.filename)[1] if ext.lower() in Image.extensions: - try: - im = PILImage.open(f.src_path) - except: - self.logger.error("Failed to open %s", f.src_path) - else: - if im.size[0] > im.size[1]: - self._thumbnail = join(self.name, f.thumbnail) - self.logger.debug( - "Use 1st landscape image as thumbnail for %r :" - " %s", self, self._thumbnail) - return url_from_path(self._thumbnail) + # Use f.size if available as it is quicker (in cache), but + # 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) + + if size['width'] > size['height']: + self._thumbnail = join(self.name, f.thumbnail) + self.logger.debug( + "Use 1st landscape image as thumbnail for %r :" + " %s", self, self._thumbnail) + return url_from_path(self._thumbnail) # else simply return the 1st media file if not self._thumbnail and self.medias: diff --git a/sigal/image.py b/sigal/image.py index f91996d..d77ff08 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -152,8 +152,8 @@ def process_image(filepath, outpath, settings): if settings['make_thumbs']: thumb_name = os.path.join(outpath, get_thumb(settings, filename)) generate_thumbnail(outname, thumb_name, settings['thumb_size'], - settings['thumb_video_delay'], fit=settings['thumb_fit'], - options=options) + settings['thumb_video_delay'], + fit=settings['thumb_fit'], options=options) except Exception as e: logger.info('Failed to process: %r', e) return Status.FAILURE @@ -163,18 +163,19 @@ def process_image(filepath, outpath, settings): def get_size(file_path): """Return image size (width and height).""" - logger = logging.getLogger(__name__) try: im = PILImage.open(file_path) - except (IOError, IndexError, TypeError, AttributeError) as e : + except (IOError, IndexError, TypeError, AttributeError) as e: + logger = logging.getLogger(__name__) logger.error("Could not read size of %s due to %r", file_path, e) else: - width,height = im.size + width, height = im.size return { - 'width': width, + 'width': width, 'height': height } + def get_exif_data(filename): """Return a dict with the raw EXIF data."""