Browse Source

Merge pull request #97 from saimn/plugins

Add plugins
pull/107/head
Simon Conseil 12 years ago
parent
commit
ea23f16e39
  1. 1
      docs/index.rst
  2. 86
      docs/plugins.rst
  3. 1
      requirements.txt
  4. 2
      setup.py
  5. 31
      sigal/__init__.py
  6. 8
      sigal/gallery.py
  7. 22
      sigal/image.py
  8. 0
      sigal/plugins/__init__.py
  9. 34
      sigal/plugins/adjust.py
  10. 31
      sigal/plugins/copyright.py
  11. 5
      sigal/settings.py
  12. 10
      sigal/signals.py
  13. 24
      sigal/templates/sigal.conf.py
  14. 6
      tests/sample/sigal.conf.py

1
docs/index.rst

@ -11,4 +11,5 @@ Documentation
configuration
album_information
themes
plugins
changelog

86
docs/plugins.rst

@ -0,0 +1,86 @@
=========
Plugins
=========
How to use plugins
------------------
Plugins must be specified with the ``plugins`` setting:
.. code-block:: python
from sigal.plugins import copyright
plugins = ['sigal.plugins.adjust', copyright]
You can either specify the name of the module which contains the plugin, or
import the plugin before adding it to the list. The ``plugin_paths`` setting
can be used to specify paths to search for plugins (if they are not in the
python path).
Write a new plugin
------------------
Plugins are based on signals with the blinker_ library. A plugin must
subscribe to a signal (the list is given below). New signals can be added if
need. See an example with the copyright plugin:
.. _blinker: http://pythonhosted.org/blinker/
.. literalinclude:: ../sigal/plugins/copyright.py
:language: python
Signals
-------
.. function:: sigal.signals.album_initialized(album)
:noindex:
Called after the :class:`~sigal.gallery.Album` is initialized.
:param album: the :class:`~sigal.gallery.Album` object.
.. data:: sigal.signals.gallery_initialized(gallery)
:noindex:
Called after the gallery is initialized.
:param gallery: the :class:`Gallery` object.
.. data:: sigal.signals.media_initialized(media)
:noindex:
Called after the :class:`~sigal.gallery.Media`
(:class:`~sigal.gallery.Image` or :class:`~sigal.gallery.Video`) is
initialized.
:param media: the media object.
.. data:: sigal.signals.gallery_build(gallery)
:noindex:
Called after the gallery is build (after media are resized and html files
are written).
:param gallery: the :class:`Gallery` object.
.. data:: sigal.signals.img_resized(img, settings=settings)
:noindex:
Called after the image is resized. This signal work differently, the
modified image object must be returned by the registered funtion.
:param img: the PIL image object.
:param settings: the settings dict.
List of plugins
---------------
Adjust plugin
=============
.. automodule:: sigal.plugins.adjust
Copyright plugin
================
.. automodule:: sigal.plugins.copyright

1
requirements.txt

@ -1,4 +1,5 @@
argh
blinker
Jinja2
Markdown
Pillow

2
setup.py

@ -3,7 +3,7 @@
import os
from setuptools import setup
requires = ['argh', 'jinja2', 'Markdown', 'Pillow', 'pilkit']
requires = ['argh', 'blinker', 'jinja2', 'Markdown', 'Pillow', 'pilkit']
entry_points = {
'console_scripts': ['sigal = sigal:main']

31
sigal/__init__.py

@ -28,10 +28,12 @@ sigal is yet another python script to prepare a static gallery of images:
* resize images, create thumbnails with some options (squared thumbs, ...).
* generate html pages.
"""
from __future__ import absolute_import, print_function
import importlib
import io
import locale
import logging
@ -41,6 +43,7 @@ import time
from argh import ArghParser, arg
from .compat import server, socketserver, string_types
from .gallery import Gallery
from .log import init_logging
from .pkgmeta import __version__
@ -105,6 +108,8 @@ def build(source, destination, debug=False, verbose=False, force=False,
sys.exit(1)
locale.setlocale(locale.LC_ALL, settings['locale'])
init_plugins(settings)
gal = Gallery(settings, theme=theme, ncpu=ncpu)
gal.build(force=force)
@ -120,14 +125,36 @@ def build(source, destination, debug=False, verbose=False, force=False,
'seconds.').format(duration=time.time() - start_time, **gal.stats))
def init_plugins(settings):
"""Load plugins and call register()."""
logger = logging.getLogger(__name__)
logger.debug('Plugin paths: %s', settings['plugin_paths'])
for path in settings['plugin_paths']:
sys.path.insert(0, path)
for plugin in settings['plugins']:
try:
if isinstance(plugin, string_types):
mod = importlib.import_module(plugin)
mod.register(settings)
else:
plugin.register(settings)
logger.debug('Registered plugin %s', plugin)
except Exception as e:
logger.error('Failed to load plugin %s: %r', plugin, e)
for path in settings['plugin_paths']:
sys.path.remove(path)
@arg('path', nargs='?', default='_build',
help='Directory to serve (default: _build/)')
def serve(path):
"""Run a simple web server."""
if os.path.exists(path):
from .compat import server, socketserver
os.chdir(path)
PORT = 8000
Handler = server.SimpleHTTPRequestHandler

8
sigal/gallery.py

@ -37,7 +37,7 @@ from datetime import datetime
from os.path import isfile, join, splitext
from PIL import Image as PILImage
from . import image, video
from . import image, video, signals
from .compat import UnicodeMixin, strxfrm, url_quote
from .image import process_image, get_exif_tags
from .log import colored, BLUE
@ -80,6 +80,7 @@ class Media(UnicodeMixin):
self.raw_exif = None
self.exif = None
self.date = None
signals.media_initialized.send(self)
def __repr__(self):
return "<%s>(%r)" % (self.__class__.__name__, str(self))
@ -221,6 +222,8 @@ class Album(UnicodeMixin):
medias.sort(key=key, reverse=settings['medias_sort_reverse'])
signals.album_initialized.send(self)
def __repr__(self):
return "<%s>(path=%r, title=%r)" % (self.__class__.__name__, self.path,
self.title)
@ -458,6 +461,7 @@ class Gallery(object):
albums[relpath] = album
self.logger.debug('Albums:\n%r', albums.values())
signals.gallery_initialized.send(self)
def init_pool(self, ncpu):
try:
@ -527,6 +531,8 @@ class Gallery(object):
for album in self.albums.values():
self.writer.write(album)
signals.gallery_build.send(self)
def process_dir(self, album, force=False):
"""Process a list of images in a directory."""

22
sigal/image.py

@ -38,11 +38,11 @@ from copy import deepcopy
from datetime import datetime
from PIL.ExifTags import TAGS, GPSTAGS
from PIL import Image as PILImage
from PIL import ImageDraw, ImageOps
from pilkit.processors import Transpose, Adjust
from PIL import ImageOps
from pilkit.processors import Transpose
from pilkit.utils import save_image
from . import compat
from . import compat, signals
from .settings import get_thumb
@ -97,11 +97,10 @@ def generate_image(source, outname, settings, options=None):
processor = processor_cls(*settings['img_size'], upscale=False)
img = processor.process(img)
# Adjust the image after resizing
img = Adjust(**settings['adjust_options']).process(img)
if settings['copyright']:
add_copyright(img, settings['copyright'])
# signal.send() does not work here as plugins can modify the image, so we
# iterate other the receivers to call them with the image.
for receiver in signals.img_resized.receivers_for(img):
img = receiver(img, settings=settings)
outformat = img.format or original_format or 'JPEG'
logger.debug(u'Save resized image to {0} ({1})'.format(outname, outformat))
@ -152,13 +151,6 @@ def process_image(filepath, outpath, settings):
fit=settings['thumb_fit'], options=options)
def add_copyright(img, text):
"""Add a copyright to the image."""
draw = ImageDraw.Draw(img)
draw.text((5, img.size[1] - 15), '\xa9 ' + text)
def _get_exif_data(filename):
"""Return a dict with EXIF data."""

0
sigal/plugins/__init__.py

34
sigal/plugins/adjust.py

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
"""Plugin which adjust the image after resizing.
Based on pilkit's Adjust_ processor.
.. _Adjust: https://github.com/matthewwithanm/pilkit/blob/master/pilkit/processors/base.py#L19
Settings::
adjust_options = {'color': 1.0,
'brightness': 1.0,
'contrast': 1.0,
'sharpness': 1.0}
"""
import logging
from sigal import signals
from pilkit.processors import Adjust
logger = logging.getLogger(__name__)
def adjust(img, settings=None):
logger.debug('Adjust image %r', img)
return Adjust(**settings['adjust_options']).process(img)
def register(settings):
if settings.get('adjust_options'):
signals.img_resized.connect(adjust)
else:
logger.warning('Adjust options are not set')

31
sigal/plugins/copyright.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""Plugin which add a copyright to the image.
Settings:
- ``copyright``: the copyright text.
TODO: Add more settings (font, size, ...)
"""
import logging
from PIL import ImageDraw
from sigal import signals
logger = logging.getLogger(__name__)
def add_copyright(img, settings=None):
logger.debug('Adding copyright to %r', img)
draw = ImageDraw.Draw(img)
draw.text((5, img.size[1] - 15), settings['copyright'])
return img
def register(settings):
if settings.get('copyright'):
signals.img_resized.connect(add_copyright)
else:
logger.warning('Copyright text is not set')

5
sigal/settings.py

@ -31,13 +31,10 @@ from .compat import PY2
_DEFAULT_CONFIG = {
'adjust_options': {'color': 1.0, 'brightness': 1.0,
'contrast': 1.0, 'sharpness': 1.0},
'albums_sort_reverse': False,
'autorotate_images': True,
'colorbox_column_size': 4,
'copy_exif_data': False,
'copyright': '',
'destination': '_build',
'files_to_copy': (),
'google_analytics': '',
@ -55,6 +52,8 @@ _DEFAULT_CONFIG = {
'make_thumbs': True,
'orig_dir': 'original',
'orig_link': False,
'plugins': [],
'plugin_paths': [],
'source': '',
'theme': 'colorbox',
'thumb_dir': 'thumbnails',

10
sigal/signals.py

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
from blinker import signal
img_resized = signal('img_resized')
album_initialized = signal('album_initialized')
gallery_initialized = signal('gallery_initialized')
gallery_build = signal('gallery_build')
media_initialized = signal('media_initialized')

24
sigal/templates/sigal.conf.py

@ -27,13 +27,6 @@ img_size = (800, 600)
# - None: don't resize
# img_processor = 'ResizeToFit'
# Adjust the image after resizing it. A default value of 1.0 leaves the images
# untouched.
# adjust_options = {'color': 1.0,
# 'brightness': 1.0,
# 'contrast': 1.0,
# 'sharpness': 1.0}
# Generate thumbnails
# make_thumbs = True
@ -104,9 +97,6 @@ ignore_files = []
# links = [('Example link', 'http://example.org'),
# ('Another link', 'http://example.org')]
# Add a copyright text on the image (default: '')
# copyright = "An example copyright message"
# Google Analytics tracking code (UA-xxxx-x)
# google_analytics = ''
@ -138,3 +128,17 @@ ignore_files = []
# Then the image size must be adapted to fit the column size.
# The default is 4 columns which gives 220px. 3 columns gives 160px.
# colorbox_column_size = 4
# --------
# Plugins
# --------
# Add a copyright text on the image (default: '')
# copyright = "© An example copyright message"
# Adjust the image after resizing it. A default value of 1.0 leaves the images
# untouched.
# adjust_options = {'color': 1.0,
# 'brightness': 1.0,
# 'contrast': 1.0,
# 'sharpness': 1.0}

6
tests/sample/sigal.conf.py

@ -8,7 +8,11 @@ keep_orig = True
links = [('Example link', 'http://example.org'),
('Another link', 'http://example.org')]
copyright = "An example copyright message"
from sigal.plugins import copyright
plugins = ['sigal.plugins.adjust', copyright]
copyright = u"© An example copyright message"
adjust_options = {'color': 0.0, 'brightness': 1.0,
'contrast': 1.0, 'sharpness': 0.0}
# theme = 'galleria'
# thumb_size = (280, 210)

Loading…
Cancel
Save