Merge branch 'pilkit'
This commit is contained in:
@@ -3,4 +3,5 @@ clint
|
||||
Jinja2
|
||||
Markdown
|
||||
Pillow
|
||||
pilkit
|
||||
pytest
|
||||
|
||||
2
setup.py
2
setup.py
@@ -3,7 +3,7 @@
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
requires = ['argh', 'clint', 'jinja2', 'Markdown']
|
||||
requires = ['argh', 'clint', 'jinja2', 'Markdown', 'pilkit']
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageOps # NOQA
|
||||
|
||||
@@ -26,6 +26,7 @@ import codecs
|
||||
import logging
|
||||
import markdown
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from clint.textui import progress, colored
|
||||
@@ -33,7 +34,7 @@ from multiprocessing import Pool
|
||||
from os.path import join
|
||||
from PIL import Image as PILImage
|
||||
|
||||
from .image import Image
|
||||
from .image import generate_image, generate_thumbnail
|
||||
from .settings import get_thumb
|
||||
from .writer import Writer
|
||||
|
||||
@@ -211,7 +212,6 @@ class Gallery(object):
|
||||
for f in img_iterator:
|
||||
filename = os.path.split(f)[1]
|
||||
outname = join(outpath, filename)
|
||||
self.logger.info(filename)
|
||||
|
||||
if os.path.isfile(outname) and not self.force:
|
||||
self.logger.info("%s exists - skipping", filename)
|
||||
@@ -239,25 +239,25 @@ def process_image(filepath, outpath, settings):
|
||||
|
||||
filename = os.path.split(filepath)[1]
|
||||
outname = join(outpath, filename)
|
||||
ext = os.path.splitext(filename)
|
||||
|
||||
img = Image(filepath)
|
||||
if ext in ['.jpg', '.jpeg', '.JPG', '.JPEG']:
|
||||
options = settings['jpg_options']
|
||||
elif ext == '.png':
|
||||
options = {'optimize': True}
|
||||
else:
|
||||
options = {}
|
||||
|
||||
if settings['keep_orig']:
|
||||
img.save(join(outpath, settings['orig_dir'], filename),
|
||||
**settings['jpg_options'])
|
||||
shutil.copy(filepath, join(outpath, settings['orig_dir'], filename))
|
||||
|
||||
img.resize(settings['img_size'])
|
||||
|
||||
if settings['copyright']:
|
||||
img.add_copyright(settings['copyright'])
|
||||
|
||||
img.save(outname, **settings['jpg_options'])
|
||||
generate_image(filepath, outname, settings['img_size'], None,
|
||||
options=options, copyright_text=settings['copyright'])
|
||||
|
||||
if settings['make_thumbs']:
|
||||
thumb_name = join(outpath, get_thumb(settings, filename))
|
||||
img.thumbnail(thumb_name, settings['thumb_size'],
|
||||
fit=settings['thumb_fit'],
|
||||
quality=settings['jpg_options']['quality'])
|
||||
generate_thumbnail(outname, thumb_name, settings['thumb_size'], None,
|
||||
fit=settings['thumb_fit'], options=options)
|
||||
|
||||
|
||||
def get_metadata(path):
|
||||
@@ -266,8 +266,8 @@ def get_metadata(path):
|
||||
- title
|
||||
- thumbnail image
|
||||
- description
|
||||
"""
|
||||
|
||||
"""
|
||||
descfile = join(path, DESCRIPTION_FILE)
|
||||
|
||||
if not os.path.isfile(descfile):
|
||||
@@ -279,11 +279,10 @@ def get_metadata(path):
|
||||
'thumbnail': ''
|
||||
}
|
||||
else:
|
||||
md = markdown.Markdown(extensions=['meta'])
|
||||
|
||||
with codecs.open(descfile, "r", "utf-8") as f:
|
||||
text = f.read()
|
||||
|
||||
md = markdown.Markdown(extensions=['meta'])
|
||||
html = md.convert(text)
|
||||
|
||||
meta = {
|
||||
|
||||
154
sigal/image.py
154
sigal/image.py
@@ -20,128 +20,64 @@
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from exceptions import IOError
|
||||
from PIL import Image as PILImage
|
||||
from PIL import ImageDraw, ImageOps, ImageFile
|
||||
|
||||
# EXIF specs Orientation constant
|
||||
EXIF_ORIENTATION_TAG = 274
|
||||
from PIL import ImageDraw, ImageOps
|
||||
from pilkit.processors import ProcessorPipeline, Transpose, ResizeToFill
|
||||
from pilkit.utils import save_image
|
||||
|
||||
|
||||
class Image(object):
|
||||
"""Image container
|
||||
def generate_image(source, outname, size, format, options=None,
|
||||
autoconvert=True, copyright_text=''):
|
||||
"""Image processor, rotate and resize the image.
|
||||
|
||||
Prepare images: resize images, and create thumbnails with some options
|
||||
(squared thumbs, ...).
|
||||
|
||||
:param filename: path to an image
|
||||
:param source: path to an image
|
||||
:param options: dict with PIL options (quality, optimize, progressive)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.imgname = os.path.split(filename)[1]
|
||||
self.logger = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
img = PILImage.open(source)
|
||||
original_format = img.format
|
||||
|
||||
with open(filename, 'rb') as fp:
|
||||
self.img = PILImage.open(fp)
|
||||
self.img.load()
|
||||
# Rotate the img, and catch IOError when PIL fails to read EXIF
|
||||
try:
|
||||
processor = Transpose()
|
||||
img = processor.process(img)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
# Try to read exif metadata. This can fail if:
|
||||
# - the image does not have exif (png files) -> AttributeError
|
||||
# - PIL fail to read exif -> IOError
|
||||
try:
|
||||
exif = self.img._getexif()
|
||||
except (IOError, AttributeError):
|
||||
exif = False
|
||||
# Run the other processors
|
||||
processors = [ResizeToFill(*size)]
|
||||
img = ProcessorPipeline(processors).process(img)
|
||||
|
||||
if exif:
|
||||
# http://www.impulseadventure.com/photo/exif-orientation.html
|
||||
orientation = exif.get(EXIF_ORIENTATION_TAG)
|
||||
rotate_map = {3: 180, 6: -90, 8: 90}
|
||||
rotation = rotate_map.get(orientation)
|
||||
if rotation:
|
||||
self.img = self.img.rotate(rotation)
|
||||
if copyright_text:
|
||||
add_copyright(img, copyright_text)
|
||||
|
||||
def save(self, filename, **kwargs):
|
||||
"""Save the image.
|
||||
|
||||
Pass a dict with PIL options (quality, optimize, progressive). PIL can
|
||||
have problems saving large JPEGs if MAXBLOCK isn't big enough, So if
|
||||
we have a problem saving, we temporarily increase it. See
|
||||
http://github.com/jdriscoll/django-imagekit/issues/91
|
||||
|
||||
"""
|
||||
try:
|
||||
with quiet():
|
||||
self.img.save(filename, "JPEG", **kwargs)
|
||||
except IOError:
|
||||
old_maxblock = ImageFile.MAXBLOCK
|
||||
ImageFile.MAXBLOCK = self.img.size[0] * self.img.size[1]
|
||||
try:
|
||||
self.img.save(filename, "JPEG", **kwargs)
|
||||
finally:
|
||||
ImageFile.MAXBLOCK = old_maxblock
|
||||
|
||||
def resize(self, size):
|
||||
"""Resize the image.
|
||||
|
||||
- check if the image format is portrait or landscape and adjust `size`.
|
||||
- compute the width and height ratio, and keep the min to resize the
|
||||
image inside the `size` box without distorting it.
|
||||
|
||||
:param size: tuple with the (with, height) to resize
|
||||
|
||||
"""
|
||||
|
||||
if self.img.size[0] > self.img.size[1]:
|
||||
newsize = size
|
||||
else:
|
||||
newsize = (size[1], size[0])
|
||||
|
||||
wratio = newsize[0] / self.img.size[0]
|
||||
hratio = newsize[1] / self.img.size[1]
|
||||
ratio = min(wratio, hratio)
|
||||
newsize = (int(ratio * self.img.size[0]),
|
||||
int(ratio * self.img.size[1]))
|
||||
|
||||
if ratio < 1:
|
||||
self.img = self.img.resize(newsize, PILImage.ANTIALIAS)
|
||||
|
||||
def add_copyright(self, text):
|
||||
"Add a copyright to the image"
|
||||
|
||||
draw = ImageDraw.Draw(self.img)
|
||||
draw.text((5, self.img.size[1] - 15), '\xa9 ' + text)
|
||||
|
||||
def thumbnail(self, filename, box, fit=True, quality=90):
|
||||
"Create a thumbnail image"
|
||||
|
||||
if fit:
|
||||
self.img = ImageOps.fit(self.img, box, PILImage.ANTIALIAS)
|
||||
else:
|
||||
self.img.thumbnail(box, PILImage.ANTIALIAS)
|
||||
|
||||
self.img.save(filename, quality=quality)
|
||||
format = format or img.format or original_format or 'JPEG'
|
||||
logger.debug('Save resized image to {0} ({1})'.format(outname, format))
|
||||
save_image(img, outname, format, options=options, autoconvert=autoconvert)
|
||||
|
||||
|
||||
class quiet(object):
|
||||
"""A context manager for suppressing the stderr activity of PIL's C
|
||||
libraries. Based on http://stackoverflow.com/a/978264/155370
|
||||
def generate_thumbnail(source, outname, box, format, fit=True, options=None):
|
||||
"Create a thumbnail image"
|
||||
|
||||
"""
|
||||
def __enter__(self):
|
||||
self.stderr_fd = sys.__stderr__.fileno()
|
||||
self.null_fd = os.open(os.devnull, os.O_RDWR)
|
||||
self.old = os.dup(self.stderr_fd)
|
||||
os.dup2(self.null_fd, self.stderr_fd)
|
||||
logger = logging.getLogger(__name__)
|
||||
img = PILImage.open(source)
|
||||
original_format = img.format
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
os.dup2(self.old, self.stderr_fd)
|
||||
os.close(self.null_fd)
|
||||
os.close(self.old)
|
||||
if fit:
|
||||
img = ImageOps.fit(img, box, PILImage.ANTIALIAS)
|
||||
else:
|
||||
img.thumbnail(box, PILImage.ANTIALIAS)
|
||||
|
||||
format = format or img.format or original_format or 'JPEG'
|
||||
logger.debug('Save thumnail image to {0} ({1})'.format(outname, format))
|
||||
save_image(img, outname, format, options=options, autoconvert=True)
|
||||
|
||||
|
||||
def add_copyright(img, text):
|
||||
"Add a copyright to the image"
|
||||
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.text((5, img.size[1] - 15), '\xa9 ' + text)
|
||||
|
||||
@@ -33,7 +33,7 @@ from distutils.dir_util import copy_tree
|
||||
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, PrefixLoader
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
|
||||
from .image import Image
|
||||
from .image import generate_thumbnail
|
||||
from .settings import get_thumb
|
||||
from .pkgmeta import __url__ as sigal_link
|
||||
|
||||
@@ -147,10 +147,11 @@ class Writer(object):
|
||||
# generate the thumbnail if it is missing (if
|
||||
# settings['make_thumbs'] is False)
|
||||
if not os.path.exists(thumb_path):
|
||||
img = Image(os.path.join(self.output_dir, dpath, alb_thumb))
|
||||
img.thumbnail(thumb_path, self.settings['thumb_size'],
|
||||
fit=self.settings['thumb_fit'],
|
||||
quality=self.settings['jpg_quality'])
|
||||
source = os.path.join(self.output_dir, dpath, alb_thumb)
|
||||
generate_thumbnail(
|
||||
source, thumb_path, self.settings['thumb_size'],
|
||||
fit=self.settings['thumb_fit'],
|
||||
quality=self.settings['jpg_options']['quality'])
|
||||
|
||||
ctx['albums'].append({
|
||||
'url': d + '/' + self.url_ext,
|
||||
|
||||
@@ -1,35 +1,18 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import os
|
||||
from tempfile import mkdtemp
|
||||
from shutil import rmtree
|
||||
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest # NOQA
|
||||
|
||||
from sigal.image import Image
|
||||
from sigal.image import generate_image, generate_thumbnail
|
||||
|
||||
CURRENT_DIR = os.path.dirname(__file__)
|
||||
TEST_IMAGE = 'exo20101028-b-full.jpg'
|
||||
|
||||
|
||||
class TestImage(unittest.TestCase):
|
||||
def test_image(tmpdir):
|
||||
"Test the Image class."
|
||||
|
||||
def setUp(self):
|
||||
self.temp_path = mkdtemp()
|
||||
self.srcfile = os.path.join(CURRENT_DIR, 'sample', 'dir2', TEST_IMAGE)
|
||||
self.dstfile = os.path.join(self.temp_path, TEST_IMAGE)
|
||||
self.img = Image(self.srcfile)
|
||||
srcfile = os.path.join(CURRENT_DIR, 'sample', 'dir2', TEST_IMAGE)
|
||||
dstfile = str(tmpdir.join(TEST_IMAGE))
|
||||
|
||||
def tearDown(self):
|
||||
rmtree(self.temp_path)
|
||||
|
||||
def test_imgname(self):
|
||||
self.assertEqual(self.img.imgname, TEST_IMAGE)
|
||||
|
||||
def test_save(self):
|
||||
self.img.save(self.dstfile)
|
||||
self.assertTrue(os.path.isfile(self.dstfile))
|
||||
generate_thumbnail(srcfile, dstfile, (200, 150))
|
||||
assert os.path.isfile(dstfile)
|
||||
|
||||
Reference in New Issue
Block a user