@@ -11,4 +11,5 @@ Documentation
|
||||
configuration
|
||||
album_information
|
||||
themes
|
||||
plugins
|
||||
changelog
|
||||
|
||||
86
docs/plugins.rst
Normal file
86
docs/plugins.rst
Normal file
@@ -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,4 +1,5 @@
|
||||
argh
|
||||
blinker
|
||||
Jinja2
|
||||
Markdown
|
||||
Pillow
|
||||
|
||||
2
setup.py
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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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
Normal file
0
sigal/plugins/__init__.py
Normal file
34
sigal/plugins/adjust.py
Normal file
34
sigal/plugins/adjust.py
Normal file
@@ -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
Normal file
31
sigal/plugins/copyright.py
Normal file
@@ -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')
|
||||
@@ -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
Normal file
10
sigal/signals.py
Normal file
@@ -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')
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user