adding the thumbnail and summary plugin
56
3-column-plus/pelican-plugins/summary/Readme.rst
Normal file
@ -0,0 +1,56 @@
|
||||
Summary
|
||||
-------
|
||||
|
||||
This plugin allows easy, variable length summaries directly embedded into the
|
||||
body of your articles. It introduces two new settings: ``SUMMARY_BEGIN_MARKER``
|
||||
and ``SUMMARY_END_MARKER``: strings which can be placed directly into an article
|
||||
to mark the beginning and end of a summary. When found, the standard
|
||||
``SUMMARY_MAX_LENGTH`` setting will be ignored. The markers themselves will also
|
||||
be removed from your articles before they are published. The default values
|
||||
are ``<!-- PELICAN_BEGIN_SUMMARY -->`` and ``<!-- PELICAN_END_SUMMARY -->``.
|
||||
For example::
|
||||
|
||||
Title: My super title
|
||||
Date: 2010-12-03 10:20
|
||||
Tags: thats, awesome
|
||||
Category: yeah
|
||||
Slug: my-super-post
|
||||
Author: Alexis Metaireau
|
||||
|
||||
This is the content of my super blog post.
|
||||
<!-- PELICAN_END_SUMMARY -->
|
||||
and this content occurs after the summary.
|
||||
|
||||
Here, the summary is taken to be the first line of the post. Because no
|
||||
beginning marker was found, it starts at the top of the body. It is possible
|
||||
to leave out the end marker instead, in which case the summary will start at the
|
||||
beginning marker and continue to the end of the body.
|
||||
|
||||
If no beginning or end marker is found, and if ``SUMMARY_USE_FIRST_PARAGRAPH``
|
||||
is enabled in the settings, the summary will be the first paragraph of the post.
|
||||
|
||||
The plugin also sets a ``has_summary`` attribute on every article. It is True
|
||||
for articles with an explicitly-defined summary, and False otherwise. (It is
|
||||
also False for an article truncated by ``SUMMARY_MAX_LENGTH``.) Your templates
|
||||
can use this e.g. to add a link to the full text at the end of the summary.
|
||||
|
||||
reST example
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Inserting the markers into a reStructuredText document makes use of the
|
||||
comment directive, because raw HTML is automatically escaped. The reST equivalent of the above Markdown example looks like this::
|
||||
|
||||
My super title
|
||||
##############
|
||||
|
||||
:date: 2010-12-03 10:20
|
||||
:tags: thats, awesome
|
||||
:category: yeah
|
||||
:slug: my-super-post
|
||||
:author: Alexis Metaireau
|
||||
|
||||
This is the content of my super blog post.
|
||||
|
||||
.. PELICAN_END_SUMMARY
|
||||
|
||||
and this content occurs after the summary.
|
1
3-column-plus/pelican-plugins/summary/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .summary import *
|
105
3-column-plus/pelican-plugins/summary/summary.py
Normal file
@ -0,0 +1,105 @@
|
||||
"""
|
||||
Summary
|
||||
-------
|
||||
|
||||
This plugin allows easy, variable length summaries directly embedded into the
|
||||
body of your articles.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from pelican import signals
|
||||
from pelican.generators import ArticlesGenerator, StaticGenerator, PagesGenerator
|
||||
import re
|
||||
|
||||
def initialized(pelican):
|
||||
from pelican.settings import DEFAULT_CONFIG
|
||||
DEFAULT_CONFIG.setdefault('SUMMARY_BEGIN_MARKER',
|
||||
'<!-- PELICAN_BEGIN_SUMMARY -->')
|
||||
DEFAULT_CONFIG.setdefault('SUMMARY_END_MARKER',
|
||||
'<!-- PELICAN_END_SUMMARY -->')
|
||||
DEFAULT_CONFIG.setdefault('SUMMARY_USE_FIRST_PARAGRAPH', False)
|
||||
if pelican:
|
||||
pelican.settings.setdefault('SUMMARY_BEGIN_MARKER',
|
||||
'<!-- PELICAN_BEGIN_SUMMARY -->')
|
||||
pelican.settings.setdefault('SUMMARY_END_MARKER',
|
||||
'<!-- PELICAN_END_SUMMARY -->')
|
||||
pelican.settings.setdefault('SUMMARY_USE_FIRST_PARAGRAPH', False)
|
||||
|
||||
def extract_summary(instance):
|
||||
# if summary is already specified, use it
|
||||
# if there is no content, there's nothing to do
|
||||
if hasattr(instance, '_summary'):
|
||||
instance.has_summary = True
|
||||
return
|
||||
|
||||
if not instance._content:
|
||||
instance.has_summary = False
|
||||
return
|
||||
|
||||
begin_marker = instance.settings['SUMMARY_BEGIN_MARKER']
|
||||
end_marker = instance.settings['SUMMARY_END_MARKER']
|
||||
use_first_paragraph = instance.settings['SUMMARY_USE_FIRST_PARAGRAPH']
|
||||
remove_markers = True
|
||||
|
||||
content = instance._content
|
||||
begin_summary = -1
|
||||
end_summary = -1
|
||||
if begin_marker:
|
||||
begin_summary = content.find(begin_marker)
|
||||
if end_marker:
|
||||
end_summary = content.find(end_marker)
|
||||
|
||||
if begin_summary == -1 and end_summary == -1 and use_first_paragraph:
|
||||
begin_marker, end_marker = '<p>', '</p>'
|
||||
remove_markers = False
|
||||
begin_summary = content.find(begin_marker)
|
||||
end_summary = content.find(end_marker)
|
||||
|
||||
if begin_summary == -1 and end_summary == -1:
|
||||
instance.has_summary = False
|
||||
return
|
||||
|
||||
# skip over the begin marker, if present
|
||||
if begin_summary == -1:
|
||||
begin_summary = 0
|
||||
else:
|
||||
begin_summary = begin_summary + len(begin_marker)
|
||||
|
||||
if end_summary == -1:
|
||||
end_summary = None
|
||||
|
||||
summary = content[begin_summary:end_summary]
|
||||
|
||||
if remove_markers:
|
||||
# remove the markers from the content
|
||||
if begin_summary:
|
||||
content = content.replace(begin_marker, '', 1)
|
||||
if end_summary:
|
||||
content = content.replace(end_marker, '', 1)
|
||||
|
||||
summary = re.sub(r"<div.*>", "", summary)
|
||||
summary = re.sub(r"</div>", "", summary)
|
||||
|
||||
instance._content = content
|
||||
instance._summary = summary
|
||||
instance.has_summary = True
|
||||
|
||||
|
||||
def run_plugin(generators):
|
||||
for generator in generators:
|
||||
if isinstance(generator, ArticlesGenerator):
|
||||
for article in generator.articles:
|
||||
extract_summary(article)
|
||||
elif isinstance(generator, PagesGenerator):
|
||||
for page in generator.pages:
|
||||
extract_summary(page)
|
||||
|
||||
|
||||
def register():
|
||||
signals.initialized.connect(initialized)
|
||||
try:
|
||||
signals.all_generators_finalized.connect(run_plugin)
|
||||
except AttributeError:
|
||||
# NOTE: This results in #314 so shouldn't really be relied on
|
||||
# https://github.com/getpelican/pelican-plugins/issues/314
|
||||
signals.content_object_init.connect(extract_summary)
|
96
3-column-plus/pelican-plugins/summary/test_summary.py
Normal file
@ -0,0 +1,96 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import unittest
|
||||
|
||||
from jinja2.utils import generate_lorem_ipsum
|
||||
|
||||
# generate one paragraph, enclosed with <p>
|
||||
TEST_CONTENT = str(generate_lorem_ipsum(n=1))
|
||||
TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False)
|
||||
|
||||
|
||||
from pelican.contents import Page
|
||||
import pelican.settings
|
||||
|
||||
import summary
|
||||
|
||||
class TestSummary(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(TestSummary, self).setUp()
|
||||
pelican.settings.DEFAULT_CONFIG['SUMMARY_MAX_LENGTH'] = None
|
||||
pelican.settings.DEFAULT_CONFIG['SUMMARY_USE_FIRST_PARAGRAPH'] = False
|
||||
|
||||
summary.register()
|
||||
summary.initialized(None)
|
||||
self.page_kwargs = {
|
||||
'content': TEST_CONTENT,
|
||||
'context': {
|
||||
'localsiteurl': '',
|
||||
},
|
||||
'metadata': {
|
||||
'summary': TEST_SUMMARY,
|
||||
'title': 'foo bar',
|
||||
'author': 'Blogger',
|
||||
},
|
||||
}
|
||||
|
||||
def _copy_page_kwargs(self):
|
||||
# make a deep copy of page_kwargs
|
||||
page_kwargs = dict([(key, self.page_kwargs[key]) for key in
|
||||
self.page_kwargs])
|
||||
for key in page_kwargs:
|
||||
if not isinstance(page_kwargs[key], dict):
|
||||
break
|
||||
page_kwargs[key] = dict([(subkey, page_kwargs[key][subkey])
|
||||
for subkey in page_kwargs[key]])
|
||||
|
||||
return page_kwargs
|
||||
|
||||
def test_end_summary(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
page_kwargs['content'] = (
|
||||
TEST_SUMMARY + '<!-- PELICAN_END_SUMMARY -->' + TEST_CONTENT)
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||
self.assertEqual(page.content, TEST_SUMMARY + TEST_CONTENT)
|
||||
|
||||
def test_begin_summary(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
page_kwargs['content'] = (
|
||||
'FOOBAR<!-- PELICAN_BEGIN_SUMMARY -->' + TEST_CONTENT)
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_CONTENT)
|
||||
self.assertEqual(page.content, 'FOOBAR' + TEST_CONTENT)
|
||||
|
||||
def test_begin_end_summary(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
page_kwargs['content'] = (
|
||||
'FOOBAR<!-- PELICAN_BEGIN_SUMMARY -->' + TEST_SUMMARY +
|
||||
'<!-- PELICAN_END_SUMMARY -->' + TEST_CONTENT)
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||
self.assertEqual(page.content, 'FOOBAR' + TEST_SUMMARY + TEST_CONTENT)
|
||||
|
||||
def test_use_first_paragraph(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
pelican.settings.DEFAULT_CONFIG['SUMMARY_USE_FIRST_PARAGRAPH'] = True
|
||||
page_kwargs['content'] = '<p>' + TEST_SUMMARY + '</p>' + TEST_CONTENT
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||
self.assertEqual(page.content, '<p>' + TEST_SUMMARY + '</p>' + TEST_CONTENT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
33
3-column-plus/pelican-plugins/thumbnailer/Readme.md
Normal file
@ -0,0 +1,33 @@
|
||||
Thumbnail Creation of images
|
||||
============================
|
||||
|
||||
This plugin creates thumbnails for all of the images found under a specific directory, in various thumbnail sizes.
|
||||
It requires `PIL` to function properly since `PIL` is used to resize the images, and the thumbnail will only be re-built
|
||||
if it doesn't already exist (to save processing time).
|
||||
|
||||
Installation
|
||||
-------------
|
||||
|
||||
Set up like any other plugin, making sure to set `PLUGIN_PATH` and add `thumbnailer` to the `PLUGINS` list.
|
||||
|
||||
[PIL](http://www.pythonware.com/products/pil/) or [Pillow](http://pillow.readthedocs.org/en/latest/installation.html#)
|
||||
is required. If you choose Pillow, you need to install some additional
|
||||
[external libraries](http://www.pythonware.com/products/pil/) to add support for image types like `jpg`.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
* `IMAGE_PATH` is the path to the image directory. It should reside inside your content directory, and defaults to "pictures".
|
||||
* `THUMBNAIL_DIR` is the path to the output sub-directory where the thumbnails are generated.
|
||||
* `THUMBNAIL_SIZES` is a dictionary mapping size name to size specifications.
|
||||
The generated filename will be `originalname_thumbnailname.ext` unless `THUMBNAIL_KEEP_NAME` is set.
|
||||
* `THUMBNAIL_KEEP_NAME` is a Boolean that, if set, puts the file with the original name in a thumbnailname folder, named like the key in `THUMBNAIL_SIZES`.
|
||||
* `THUMBNAIL_KEEP_TREE` is a Boolean that, if set, saves the image directory tree.
|
||||
* `THUMBNAIL_INCLUDE_REGEX` is an optional string that is used as regular expression to restrict thumbnailing to matching files. By default all files not starting with a dot are respected.
|
||||
|
||||
Sizes can be specified using any of the following formats:
|
||||
|
||||
* wxh will resize to exactly wxh cropping as necessary to get that size
|
||||
* wx? will resize so that the width is the specified size, and the height will scale to retain aspect ratio
|
||||
* ?xh same as wx? but will height being a set size
|
||||
* s is a shorthand for wxh where w=h
|
1
3-column-plus/pelican-plugins/thumbnailer/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .thumbnailer import *
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 330 KiB |
After Width: | Height: | Size: 330 KiB |
63
3-column-plus/pelican-plugins/thumbnailer/test_thumbnails.py
Normal file
@ -0,0 +1,63 @@
|
||||
from thumbnailer import _resizer
|
||||
from unittest import TestCase, main
|
||||
import os
|
||||
from PIL import Image
|
||||
|
||||
class ThumbnailerTests(TestCase):
|
||||
|
||||
def path(self, filename):
|
||||
return os.path.join(self.img_path, filename)
|
||||
|
||||
def setUp(self):
|
||||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data")
|
||||
self.img = Image.open(self.path("sample_image.jpg"))
|
||||
|
||||
def testSquare(self):
|
||||
r = _resizer('square', '100', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((100, 100), output.size)
|
||||
|
||||
def testExact(self):
|
||||
r = _resizer('exact', '250x100', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((250, 100), output.size)
|
||||
|
||||
def testWidth(self):
|
||||
r = _resizer('aspect', '250x?', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((250, 166), output.size)
|
||||
|
||||
def testHeight(self):
|
||||
r = _resizer('aspect', '?x250', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((375, 250), output.size)
|
||||
|
||||
class ThumbnailerFilenameTest(TestCase):
|
||||
|
||||
def path(self, *parts):
|
||||
return os.path.join(self.img_path, *parts)
|
||||
|
||||
def setUp(self):
|
||||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data")
|
||||
|
||||
def testRoot(self):
|
||||
"""Test a file that is in the root of img_path."""
|
||||
|
||||
r = _resizer('square', '100', self.img_path)
|
||||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg'))
|
||||
self.assertEqual('sample_image_square.jpg', new_name)
|
||||
|
||||
def testRootWithSlash(self):
|
||||
r = _resizer('square', '100', self.img_path + '/')
|
||||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg'))
|
||||
self.assertEqual('sample_image_square.jpg', new_name)
|
||||
|
||||
def testSubdir(self):
|
||||
"""Test a file that is in a sub-directory of img_path."""
|
||||
|
||||
r = _resizer('square', '100', self.img_path)
|
||||
new_name = r.get_thumbnail_name(self.path('subdir', 'sample_image.jpg'))
|
||||
self.assertEqual('subdir/sample_image_square.jpg', new_name)
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
208
3-column-plus/pelican-plugins/thumbnailer/thumbnailer.py
Normal file
@ -0,0 +1,208 @@
|
||||
import os
|
||||
import os.path as path
|
||||
import re
|
||||
from pelican import signals
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageOps
|
||||
enabled = True
|
||||
except ImportError:
|
||||
logging.warning("Unable to load PIL, disabling thumbnailer")
|
||||
enabled = False
|
||||
|
||||
DEFAULT_IMAGE_DIR = "pictures"
|
||||
DEFAULT_THUMBNAIL_DIR = "thumbnails"
|
||||
DEFAULT_THUMBNAIL_SIZES = {
|
||||
'thumbnail_square': '150',
|
||||
'thumbnail_wide': '150x?',
|
||||
'thumbnail_tall': '?x150',
|
||||
}
|
||||
DEFAULT_TEMPLATE = """<a href="{url}" rel="shadowbox" title="{filename}"><img src="{thumbnail}" alt="{filename}"></a>"""
|
||||
DEFAULT_GALLERY_THUMB = "thumbnail_square"
|
||||
|
||||
class _resizer(object):
|
||||
""" Resizes based on a text specification, see readme """
|
||||
|
||||
REGEX = re.compile(r'(\d+|\?)x(\d+|\?)')
|
||||
|
||||
def __init__(self, name, spec, root):
|
||||
self._name = name
|
||||
self._spec = spec
|
||||
# The location of input images from _image_path.
|
||||
self._root = root
|
||||
|
||||
def _null_resize(self, w, h, image):
|
||||
return image
|
||||
|
||||
def _exact_resize(self, w, h, image):
|
||||
retval = ImageOps.fit(image, (w,h), Image.BICUBIC)
|
||||
return retval
|
||||
|
||||
def _aspect_resize(self, w, h, image):
|
||||
retval = image.copy()
|
||||
retval.thumbnail((w, h), Image.ANTIALIAS)
|
||||
|
||||
return retval
|
||||
|
||||
def resize(self, image):
|
||||
resizer = self._null_resize
|
||||
|
||||
# Square resize and crop
|
||||
if 'x' not in self._spec:
|
||||
resizer = self._exact_resize
|
||||
targetw = int(self._spec)
|
||||
targeth = targetw
|
||||
else:
|
||||
matches = self.REGEX.search(self._spec)
|
||||
tmpw = matches.group(1)
|
||||
tmph = matches.group(2)
|
||||
|
||||
# Full Size
|
||||
if tmpw == '?' and tmph == '?':
|
||||
targetw = image.size[0]
|
||||
targeth = image.size[1]
|
||||
resizer = self._null_resize
|
||||
|
||||
# Set Height Size
|
||||
if tmpw == '?':
|
||||
targetw = image.size[0]
|
||||
targeth = int(tmph)
|
||||
resizer = self._aspect_resize
|
||||
|
||||
# Set Width Size
|
||||
elif tmph == '?':
|
||||
targetw = int(tmpw)
|
||||
targeth = image.size[1]
|
||||
resizer = self._aspect_resize
|
||||
|
||||
# Scale and Crop
|
||||
else:
|
||||
targetw = int(tmpw)
|
||||
targeth = int(tmph)
|
||||
resizer = self._exact_resize
|
||||
|
||||
logging.debug("Using resizer {0}".format(resizer.__name__))
|
||||
return resizer(targetw, targeth, image)
|
||||
|
||||
def get_thumbnail_name(self, in_path):
|
||||
# Find the partial path + filename beyond the input image directory.
|
||||
prefix = path.commonprefix([in_path, self._root])
|
||||
new_filename = in_path[len(prefix) + 1:]
|
||||
|
||||
# Generate the new filename.
|
||||
(basename, ext) = path.splitext(new_filename)
|
||||
return "{0}_{1}{2}".format(basename, self._name, ext)
|
||||
|
||||
def resize_file_to(self, in_path, out_path, keep_filename=False):
|
||||
""" Given a filename, resize and save the image per the specification into out_path
|
||||
|
||||
:param in_path: path to image file to save. Must be supported by PIL
|
||||
:param out_path: path to the directory root for the outputted thumbnails to be stored
|
||||
:return: None
|
||||
"""
|
||||
if keep_filename:
|
||||
filename = path.join(out_path, path.basename(in_path))
|
||||
else:
|
||||
filename = path.join(out_path, self.get_thumbnail_name(in_path))
|
||||
out_path = path.dirname(filename)
|
||||
if not path.exists(out_path):
|
||||
os.makedirs(out_path)
|
||||
if not path.exists(filename):
|
||||
try:
|
||||
image = Image.open(in_path)
|
||||
thumbnail = self.resize(image)
|
||||
thumbnail.save(filename)
|
||||
logger.info("Generated Thumbnail {0}".format(path.basename(filename)))
|
||||
except IOError:
|
||||
logger.info("Generating Thumbnail for {0} skipped".format(path.basename(filename)))
|
||||
|
||||
|
||||
def resize_thumbnails(pelican):
|
||||
""" Resize a directory tree full of images into thumbnails
|
||||
|
||||
:param pelican: The pelican instance
|
||||
:return: None
|
||||
"""
|
||||
global enabled
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
in_path = _image_path(pelican)
|
||||
|
||||
include_regex = pelican.settings.get('THUMBNAIL_INCLUDE_REGEX')
|
||||
if include_regex:
|
||||
pattern = re.compile(include_regex)
|
||||
is_included = lambda name: pattern.match(name)
|
||||
else:
|
||||
is_included = lambda name: not name.startswith('.')
|
||||
|
||||
sizes = pelican.settings.get('THUMBNAIL_SIZES', DEFAULT_THUMBNAIL_SIZES)
|
||||
resizers = dict((k, _resizer(k, v, in_path)) for k,v in sizes.items())
|
||||
logger.debug("Thumbnailer Started")
|
||||
for dirpath, _, filenames in os.walk(in_path):
|
||||
for filename in filenames:
|
||||
if is_included(filename):
|
||||
for name, resizer in resizers.items():
|
||||
in_filename = path.join(dirpath, filename)
|
||||
out_path = get_out_path(pelican, in_path, in_filename, name)
|
||||
resizer.resize_file_to(
|
||||
in_filename,
|
||||
out_path, pelican.settings.get('THUMBNAIL_KEEP_NAME'))
|
||||
|
||||
|
||||
def get_out_path(pelican, in_path, in_filename, name):
|
||||
base_out_path = path.join(pelican.settings['OUTPUT_PATH'],
|
||||
pelican.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR))
|
||||
logger.debug("Processing thumbnail {0}=>{1}".format(in_filename, name))
|
||||
if pelican.settings.get('THUMBNAIL_KEEP_NAME', False):
|
||||
if pelican.settings.get('THUMBNAIL_KEEP_TREE', False):
|
||||
return path.join(base_out_path, name, path.dirname(path.relpath(in_filename, in_path)))
|
||||
else:
|
||||
return path.join(base_out_path, name)
|
||||
else:
|
||||
return base_out_path
|
||||
|
||||
|
||||
def _image_path(pelican):
|
||||
return path.join(pelican.settings['PATH'],
|
||||
pelican.settings.get("IMAGE_PATH", DEFAULT_IMAGE_DIR)).rstrip('/')
|
||||
|
||||
|
||||
def expand_gallery(generator, metadata):
|
||||
""" Expand a gallery tag to include all of the files in a specific directory under IMAGE_PATH
|
||||
|
||||
:param pelican: The pelican instance
|
||||
:return: None
|
||||
"""
|
||||
if "gallery" not in metadata or metadata['gallery'] is None:
|
||||
return # If no gallery specified, we do nothing
|
||||
|
||||
lines = [ ]
|
||||
base_path = _image_path(generator)
|
||||
in_path = path.join(base_path, metadata['gallery'])
|
||||
template = generator.settings.get('GALLERY_TEMPLATE', DEFAULT_TEMPLATE)
|
||||
thumbnail_name = generator.settings.get("GALLERY_THUMBNAIL", DEFAULT_GALLERY_THUMB)
|
||||
thumbnail_prefix = generator.settings.get("")
|
||||
resizer = _resizer(thumbnail_name, '?x?', base_path)
|
||||
for dirpath, _, filenames in os.walk(in_path):
|
||||
for filename in filenames:
|
||||
if not filename.startswith('.'):
|
||||
url = path.join(dirpath, filename).replace(base_path, "")[1:]
|
||||
url = path.join('/static', generator.settings.get('IMAGE_PATH', DEFAULT_IMAGE_DIR), url).replace('\\', '/')
|
||||
logger.debug("GALLERY: {0}".format(url))
|
||||
thumbnail = resizer.get_thumbnail_name(filename)
|
||||
thumbnail = path.join('/', generator.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR), thumbnail).replace('\\', '/')
|
||||
lines.append(template.format(
|
||||
filename=filename,
|
||||
url=url,
|
||||
thumbnail=thumbnail,
|
||||
))
|
||||
metadata['gallery_content'] = "\n".join(lines)
|
||||
|
||||
|
||||
def register():
|
||||
signals.finalized.connect(resize_thumbnails)
|
||||
signals.article_generator_context.connect(expand_gallery)
|
56
old/pelican-plugins/summary/Readme.rst
Normal file
@ -0,0 +1,56 @@
|
||||
Summary
|
||||
-------
|
||||
|
||||
This plugin allows easy, variable length summaries directly embedded into the
|
||||
body of your articles. It introduces two new settings: ``SUMMARY_BEGIN_MARKER``
|
||||
and ``SUMMARY_END_MARKER``: strings which can be placed directly into an article
|
||||
to mark the beginning and end of a summary. When found, the standard
|
||||
``SUMMARY_MAX_LENGTH`` setting will be ignored. The markers themselves will also
|
||||
be removed from your articles before they are published. The default values
|
||||
are ``<!-- PELICAN_BEGIN_SUMMARY -->`` and ``<!-- PELICAN_END_SUMMARY -->``.
|
||||
For example::
|
||||
|
||||
Title: My super title
|
||||
Date: 2010-12-03 10:20
|
||||
Tags: thats, awesome
|
||||
Category: yeah
|
||||
Slug: my-super-post
|
||||
Author: Alexis Metaireau
|
||||
|
||||
This is the content of my super blog post.
|
||||
<!-- PELICAN_END_SUMMARY -->
|
||||
and this content occurs after the summary.
|
||||
|
||||
Here, the summary is taken to be the first line of the post. Because no
|
||||
beginning marker was found, it starts at the top of the body. It is possible
|
||||
to leave out the end marker instead, in which case the summary will start at the
|
||||
beginning marker and continue to the end of the body.
|
||||
|
||||
If no beginning or end marker is found, and if ``SUMMARY_USE_FIRST_PARAGRAPH``
|
||||
is enabled in the settings, the summary will be the first paragraph of the post.
|
||||
|
||||
The plugin also sets a ``has_summary`` attribute on every article. It is True
|
||||
for articles with an explicitly-defined summary, and False otherwise. (It is
|
||||
also False for an article truncated by ``SUMMARY_MAX_LENGTH``.) Your templates
|
||||
can use this e.g. to add a link to the full text at the end of the summary.
|
||||
|
||||
reST example
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Inserting the markers into a reStructuredText document makes use of the
|
||||
comment directive, because raw HTML is automatically escaped. The reST equivalent of the above Markdown example looks like this::
|
||||
|
||||
My super title
|
||||
##############
|
||||
|
||||
:date: 2010-12-03 10:20
|
||||
:tags: thats, awesome
|
||||
:category: yeah
|
||||
:slug: my-super-post
|
||||
:author: Alexis Metaireau
|
||||
|
||||
This is the content of my super blog post.
|
||||
|
||||
.. PELICAN_END_SUMMARY
|
||||
|
||||
and this content occurs after the summary.
|
1
old/pelican-plugins/summary/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .summary import *
|
105
old/pelican-plugins/summary/summary.py
Normal file
@ -0,0 +1,105 @@
|
||||
"""
|
||||
Summary
|
||||
-------
|
||||
|
||||
This plugin allows easy, variable length summaries directly embedded into the
|
||||
body of your articles.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from pelican import signals
|
||||
from pelican.generators import ArticlesGenerator, StaticGenerator, PagesGenerator
|
||||
import re
|
||||
|
||||
def initialized(pelican):
|
||||
from pelican.settings import DEFAULT_CONFIG
|
||||
DEFAULT_CONFIG.setdefault('SUMMARY_BEGIN_MARKER',
|
||||
'<!-- PELICAN_BEGIN_SUMMARY -->')
|
||||
DEFAULT_CONFIG.setdefault('SUMMARY_END_MARKER',
|
||||
'<!-- PELICAN_END_SUMMARY -->')
|
||||
DEFAULT_CONFIG.setdefault('SUMMARY_USE_FIRST_PARAGRAPH', False)
|
||||
if pelican:
|
||||
pelican.settings.setdefault('SUMMARY_BEGIN_MARKER',
|
||||
'<!-- PELICAN_BEGIN_SUMMARY -->')
|
||||
pelican.settings.setdefault('SUMMARY_END_MARKER',
|
||||
'<!-- PELICAN_END_SUMMARY -->')
|
||||
pelican.settings.setdefault('SUMMARY_USE_FIRST_PARAGRAPH', False)
|
||||
|
||||
def extract_summary(instance):
|
||||
# if summary is already specified, use it
|
||||
# if there is no content, there's nothing to do
|
||||
if hasattr(instance, '_summary'):
|
||||
instance.has_summary = True
|
||||
return
|
||||
|
||||
if not instance._content:
|
||||
instance.has_summary = False
|
||||
return
|
||||
|
||||
begin_marker = instance.settings['SUMMARY_BEGIN_MARKER']
|
||||
end_marker = instance.settings['SUMMARY_END_MARKER']
|
||||
use_first_paragraph = instance.settings['SUMMARY_USE_FIRST_PARAGRAPH']
|
||||
remove_markers = True
|
||||
|
||||
content = instance._content
|
||||
begin_summary = -1
|
||||
end_summary = -1
|
||||
if begin_marker:
|
||||
begin_summary = content.find(begin_marker)
|
||||
if end_marker:
|
||||
end_summary = content.find(end_marker)
|
||||
|
||||
if begin_summary == -1 and end_summary == -1 and use_first_paragraph:
|
||||
begin_marker, end_marker = '<p>', '</p>'
|
||||
remove_markers = False
|
||||
begin_summary = content.find(begin_marker)
|
||||
end_summary = content.find(end_marker)
|
||||
|
||||
if begin_summary == -1 and end_summary == -1:
|
||||
instance.has_summary = False
|
||||
return
|
||||
|
||||
# skip over the begin marker, if present
|
||||
if begin_summary == -1:
|
||||
begin_summary = 0
|
||||
else:
|
||||
begin_summary = begin_summary + len(begin_marker)
|
||||
|
||||
if end_summary == -1:
|
||||
end_summary = None
|
||||
|
||||
summary = content[begin_summary:end_summary]
|
||||
|
||||
if remove_markers:
|
||||
# remove the markers from the content
|
||||
if begin_summary:
|
||||
content = content.replace(begin_marker, '', 1)
|
||||
if end_summary:
|
||||
content = content.replace(end_marker, '', 1)
|
||||
|
||||
summary = re.sub(r"<div.*>", "", summary)
|
||||
summary = re.sub(r"</div>", "", summary)
|
||||
|
||||
instance._content = content
|
||||
instance._summary = summary
|
||||
instance.has_summary = True
|
||||
|
||||
|
||||
def run_plugin(generators):
|
||||
for generator in generators:
|
||||
if isinstance(generator, ArticlesGenerator):
|
||||
for article in generator.articles:
|
||||
extract_summary(article)
|
||||
elif isinstance(generator, PagesGenerator):
|
||||
for page in generator.pages:
|
||||
extract_summary(page)
|
||||
|
||||
|
||||
def register():
|
||||
signals.initialized.connect(initialized)
|
||||
try:
|
||||
signals.all_generators_finalized.connect(run_plugin)
|
||||
except AttributeError:
|
||||
# NOTE: This results in #314 so shouldn't really be relied on
|
||||
# https://github.com/getpelican/pelican-plugins/issues/314
|
||||
signals.content_object_init.connect(extract_summary)
|
96
old/pelican-plugins/summary/test_summary.py
Normal file
@ -0,0 +1,96 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import unittest
|
||||
|
||||
from jinja2.utils import generate_lorem_ipsum
|
||||
|
||||
# generate one paragraph, enclosed with <p>
|
||||
TEST_CONTENT = str(generate_lorem_ipsum(n=1))
|
||||
TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False)
|
||||
|
||||
|
||||
from pelican.contents import Page
|
||||
import pelican.settings
|
||||
|
||||
import summary
|
||||
|
||||
class TestSummary(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(TestSummary, self).setUp()
|
||||
pelican.settings.DEFAULT_CONFIG['SUMMARY_MAX_LENGTH'] = None
|
||||
pelican.settings.DEFAULT_CONFIG['SUMMARY_USE_FIRST_PARAGRAPH'] = False
|
||||
|
||||
summary.register()
|
||||
summary.initialized(None)
|
||||
self.page_kwargs = {
|
||||
'content': TEST_CONTENT,
|
||||
'context': {
|
||||
'localsiteurl': '',
|
||||
},
|
||||
'metadata': {
|
||||
'summary': TEST_SUMMARY,
|
||||
'title': 'foo bar',
|
||||
'author': 'Blogger',
|
||||
},
|
||||
}
|
||||
|
||||
def _copy_page_kwargs(self):
|
||||
# make a deep copy of page_kwargs
|
||||
page_kwargs = dict([(key, self.page_kwargs[key]) for key in
|
||||
self.page_kwargs])
|
||||
for key in page_kwargs:
|
||||
if not isinstance(page_kwargs[key], dict):
|
||||
break
|
||||
page_kwargs[key] = dict([(subkey, page_kwargs[key][subkey])
|
||||
for subkey in page_kwargs[key]])
|
||||
|
||||
return page_kwargs
|
||||
|
||||
def test_end_summary(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
page_kwargs['content'] = (
|
||||
TEST_SUMMARY + '<!-- PELICAN_END_SUMMARY -->' + TEST_CONTENT)
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||
self.assertEqual(page.content, TEST_SUMMARY + TEST_CONTENT)
|
||||
|
||||
def test_begin_summary(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
page_kwargs['content'] = (
|
||||
'FOOBAR<!-- PELICAN_BEGIN_SUMMARY -->' + TEST_CONTENT)
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_CONTENT)
|
||||
self.assertEqual(page.content, 'FOOBAR' + TEST_CONTENT)
|
||||
|
||||
def test_begin_end_summary(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
page_kwargs['content'] = (
|
||||
'FOOBAR<!-- PELICAN_BEGIN_SUMMARY -->' + TEST_SUMMARY +
|
||||
'<!-- PELICAN_END_SUMMARY -->' + TEST_CONTENT)
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||
self.assertEqual(page.content, 'FOOBAR' + TEST_SUMMARY + TEST_CONTENT)
|
||||
|
||||
def test_use_first_paragraph(self):
|
||||
page_kwargs = self._copy_page_kwargs()
|
||||
del page_kwargs['metadata']['summary']
|
||||
pelican.settings.DEFAULT_CONFIG['SUMMARY_USE_FIRST_PARAGRAPH'] = True
|
||||
page_kwargs['content'] = '<p>' + TEST_SUMMARY + '</p>' + TEST_CONTENT
|
||||
page = Page(**page_kwargs)
|
||||
summary.extract_summary(page)
|
||||
# test both the summary and the marker removal
|
||||
self.assertEqual(page.summary, TEST_SUMMARY)
|
||||
self.assertEqual(page.content, '<p>' + TEST_SUMMARY + '</p>' + TEST_CONTENT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
33
old/pelican-plugins/thumbnailer/Readme.md
Normal file
@ -0,0 +1,33 @@
|
||||
Thumbnail Creation of images
|
||||
============================
|
||||
|
||||
This plugin creates thumbnails for all of the images found under a specific directory, in various thumbnail sizes.
|
||||
It requires `PIL` to function properly since `PIL` is used to resize the images, and the thumbnail will only be re-built
|
||||
if it doesn't already exist (to save processing time).
|
||||
|
||||
Installation
|
||||
-------------
|
||||
|
||||
Set up like any other plugin, making sure to set `PLUGIN_PATH` and add `thumbnailer` to the `PLUGINS` list.
|
||||
|
||||
[PIL](http://www.pythonware.com/products/pil/) or [Pillow](http://pillow.readthedocs.org/en/latest/installation.html#)
|
||||
is required. If you choose Pillow, you need to install some additional
|
||||
[external libraries](http://www.pythonware.com/products/pil/) to add support for image types like `jpg`.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
* `IMAGE_PATH` is the path to the image directory. It should reside inside your content directory, and defaults to "pictures".
|
||||
* `THUMBNAIL_DIR` is the path to the output sub-directory where the thumbnails are generated.
|
||||
* `THUMBNAIL_SIZES` is a dictionary mapping size name to size specifications.
|
||||
The generated filename will be `originalname_thumbnailname.ext` unless `THUMBNAIL_KEEP_NAME` is set.
|
||||
* `THUMBNAIL_KEEP_NAME` is a Boolean that, if set, puts the file with the original name in a thumbnailname folder, named like the key in `THUMBNAIL_SIZES`.
|
||||
* `THUMBNAIL_KEEP_TREE` is a Boolean that, if set, saves the image directory tree.
|
||||
* `THUMBNAIL_INCLUDE_REGEX` is an optional string that is used as regular expression to restrict thumbnailing to matching files. By default all files not starting with a dot are respected.
|
||||
|
||||
Sizes can be specified using any of the following formats:
|
||||
|
||||
* wxh will resize to exactly wxh cropping as necessary to get that size
|
||||
* wx? will resize so that the width is the specified size, and the height will scale to retain aspect ratio
|
||||
* ?xh same as wx? but will height being a set size
|
||||
* s is a shorthand for wxh where w=h
|
1
old/pelican-plugins/thumbnailer/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .thumbnailer import *
|
BIN
old/pelican-plugins/thumbnailer/test_data/expected_exact.jpg
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
old/pelican-plugins/thumbnailer/test_data/expected_height.jpg
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
old/pelican-plugins/thumbnailer/test_data/expected_square.jpg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
old/pelican-plugins/thumbnailer/test_data/expected_width.jpg
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
old/pelican-plugins/thumbnailer/test_data/sample_image.jpg
Normal file
After Width: | Height: | Size: 330 KiB |
After Width: | Height: | Size: 330 KiB |
63
old/pelican-plugins/thumbnailer/test_thumbnails.py
Normal file
@ -0,0 +1,63 @@
|
||||
from thumbnailer import _resizer
|
||||
from unittest import TestCase, main
|
||||
import os
|
||||
from PIL import Image
|
||||
|
||||
class ThumbnailerTests(TestCase):
|
||||
|
||||
def path(self, filename):
|
||||
return os.path.join(self.img_path, filename)
|
||||
|
||||
def setUp(self):
|
||||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data")
|
||||
self.img = Image.open(self.path("sample_image.jpg"))
|
||||
|
||||
def testSquare(self):
|
||||
r = _resizer('square', '100', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((100, 100), output.size)
|
||||
|
||||
def testExact(self):
|
||||
r = _resizer('exact', '250x100', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((250, 100), output.size)
|
||||
|
||||
def testWidth(self):
|
||||
r = _resizer('aspect', '250x?', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((250, 166), output.size)
|
||||
|
||||
def testHeight(self):
|
||||
r = _resizer('aspect', '?x250', self.img_path)
|
||||
output = r.resize(self.img)
|
||||
self.assertEqual((375, 250), output.size)
|
||||
|
||||
class ThumbnailerFilenameTest(TestCase):
|
||||
|
||||
def path(self, *parts):
|
||||
return os.path.join(self.img_path, *parts)
|
||||
|
||||
def setUp(self):
|
||||
self.img_path = os.path.join(os.path.dirname(__file__), "test_data")
|
||||
|
||||
def testRoot(self):
|
||||
"""Test a file that is in the root of img_path."""
|
||||
|
||||
r = _resizer('square', '100', self.img_path)
|
||||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg'))
|
||||
self.assertEqual('sample_image_square.jpg', new_name)
|
||||
|
||||
def testRootWithSlash(self):
|
||||
r = _resizer('square', '100', self.img_path + '/')
|
||||
new_name = r.get_thumbnail_name(self.path('sample_image.jpg'))
|
||||
self.assertEqual('sample_image_square.jpg', new_name)
|
||||
|
||||
def testSubdir(self):
|
||||
"""Test a file that is in a sub-directory of img_path."""
|
||||
|
||||
r = _resizer('square', '100', self.img_path)
|
||||
new_name = r.get_thumbnail_name(self.path('subdir', 'sample_image.jpg'))
|
||||
self.assertEqual('subdir/sample_image_square.jpg', new_name)
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
208
old/pelican-plugins/thumbnailer/thumbnailer.py
Normal file
@ -0,0 +1,208 @@
|
||||
import os
|
||||
import os.path as path
|
||||
import re
|
||||
from pelican import signals
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageOps
|
||||
enabled = True
|
||||
except ImportError:
|
||||
logging.warning("Unable to load PIL, disabling thumbnailer")
|
||||
enabled = False
|
||||
|
||||
DEFAULT_IMAGE_DIR = "pictures"
|
||||
DEFAULT_THUMBNAIL_DIR = "thumbnails"
|
||||
DEFAULT_THUMBNAIL_SIZES = {
|
||||
'thumbnail_square': '150',
|
||||
'thumbnail_wide': '150x?',
|
||||
'thumbnail_tall': '?x150',
|
||||
}
|
||||
DEFAULT_TEMPLATE = """<a href="{url}" rel="shadowbox" title="{filename}"><img src="{thumbnail}" alt="{filename}"></a>"""
|
||||
DEFAULT_GALLERY_THUMB = "thumbnail_square"
|
||||
|
||||
class _resizer(object):
|
||||
""" Resizes based on a text specification, see readme """
|
||||
|
||||
REGEX = re.compile(r'(\d+|\?)x(\d+|\?)')
|
||||
|
||||
def __init__(self, name, spec, root):
|
||||
self._name = name
|
||||
self._spec = spec
|
||||
# The location of input images from _image_path.
|
||||
self._root = root
|
||||
|
||||
def _null_resize(self, w, h, image):
|
||||
return image
|
||||
|
||||
def _exact_resize(self, w, h, image):
|
||||
retval = ImageOps.fit(image, (w,h), Image.BICUBIC)
|
||||
return retval
|
||||
|
||||
def _aspect_resize(self, w, h, image):
|
||||
retval = image.copy()
|
||||
retval.thumbnail((w, h), Image.ANTIALIAS)
|
||||
|
||||
return retval
|
||||
|
||||
def resize(self, image):
|
||||
resizer = self._null_resize
|
||||
|
||||
# Square resize and crop
|
||||
if 'x' not in self._spec:
|
||||
resizer = self._exact_resize
|
||||
targetw = int(self._spec)
|
||||
targeth = targetw
|
||||
else:
|
||||
matches = self.REGEX.search(self._spec)
|
||||
tmpw = matches.group(1)
|
||||
tmph = matches.group(2)
|
||||
|
||||
# Full Size
|
||||
if tmpw == '?' and tmph == '?':
|
||||
targetw = image.size[0]
|
||||
targeth = image.size[1]
|
||||
resizer = self._null_resize
|
||||
|
||||
# Set Height Size
|
||||
if tmpw == '?':
|
||||
targetw = image.size[0]
|
||||
targeth = int(tmph)
|
||||
resizer = self._aspect_resize
|
||||
|
||||
# Set Width Size
|
||||
elif tmph == '?':
|
||||
targetw = int(tmpw)
|
||||
targeth = image.size[1]
|
||||
resizer = self._aspect_resize
|
||||
|
||||
# Scale and Crop
|
||||
else:
|
||||
targetw = int(tmpw)
|
||||
targeth = int(tmph)
|
||||
resizer = self._exact_resize
|
||||
|
||||
logging.debug("Using resizer {0}".format(resizer.__name__))
|
||||
return resizer(targetw, targeth, image)
|
||||
|
||||
def get_thumbnail_name(self, in_path):
|
||||
# Find the partial path + filename beyond the input image directory.
|
||||
prefix = path.commonprefix([in_path, self._root])
|
||||
new_filename = in_path[len(prefix) + 1:]
|
||||
|
||||
# Generate the new filename.
|
||||
(basename, ext) = path.splitext(new_filename)
|
||||
return "{0}_{1}{2}".format(basename, self._name, ext)
|
||||
|
||||
def resize_file_to(self, in_path, out_path, keep_filename=False):
|
||||
""" Given a filename, resize and save the image per the specification into out_path
|
||||
|
||||
:param in_path: path to image file to save. Must be supported by PIL
|
||||
:param out_path: path to the directory root for the outputted thumbnails to be stored
|
||||
:return: None
|
||||
"""
|
||||
if keep_filename:
|
||||
filename = path.join(out_path, path.basename(in_path))
|
||||
else:
|
||||
filename = path.join(out_path, self.get_thumbnail_name(in_path))
|
||||
out_path = path.dirname(filename)
|
||||
if not path.exists(out_path):
|
||||
os.makedirs(out_path)
|
||||
if not path.exists(filename):
|
||||
try:
|
||||
image = Image.open(in_path)
|
||||
thumbnail = self.resize(image)
|
||||
thumbnail.save(filename)
|
||||
logger.info("Generated Thumbnail {0}".format(path.basename(filename)))
|
||||
except IOError:
|
||||
logger.info("Generating Thumbnail for {0} skipped".format(path.basename(filename)))
|
||||
|
||||
|
||||
def resize_thumbnails(pelican):
|
||||
""" Resize a directory tree full of images into thumbnails
|
||||
|
||||
:param pelican: The pelican instance
|
||||
:return: None
|
||||
"""
|
||||
global enabled
|
||||
if not enabled:
|
||||
return
|
||||
|
||||
in_path = _image_path(pelican)
|
||||
|
||||
include_regex = pelican.settings.get('THUMBNAIL_INCLUDE_REGEX')
|
||||
if include_regex:
|
||||
pattern = re.compile(include_regex)
|
||||
is_included = lambda name: pattern.match(name)
|
||||
else:
|
||||
is_included = lambda name: not name.startswith('.')
|
||||
|
||||
sizes = pelican.settings.get('THUMBNAIL_SIZES', DEFAULT_THUMBNAIL_SIZES)
|
||||
resizers = dict((k, _resizer(k, v, in_path)) for k,v in sizes.items())
|
||||
logger.debug("Thumbnailer Started")
|
||||
for dirpath, _, filenames in os.walk(in_path):
|
||||
for filename in filenames:
|
||||
if is_included(filename):
|
||||
for name, resizer in resizers.items():
|
||||
in_filename = path.join(dirpath, filename)
|
||||
out_path = get_out_path(pelican, in_path, in_filename, name)
|
||||
resizer.resize_file_to(
|
||||
in_filename,
|
||||
out_path, pelican.settings.get('THUMBNAIL_KEEP_NAME'))
|
||||
|
||||
|
||||
def get_out_path(pelican, in_path, in_filename, name):
|
||||
base_out_path = path.join(pelican.settings['OUTPUT_PATH'],
|
||||
pelican.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR))
|
||||
logger.debug("Processing thumbnail {0}=>{1}".format(in_filename, name))
|
||||
if pelican.settings.get('THUMBNAIL_KEEP_NAME', False):
|
||||
if pelican.settings.get('THUMBNAIL_KEEP_TREE', False):
|
||||
return path.join(base_out_path, name, path.dirname(path.relpath(in_filename, in_path)))
|
||||
else:
|
||||
return path.join(base_out_path, name)
|
||||
else:
|
||||
return base_out_path
|
||||
|
||||
|
||||
def _image_path(pelican):
|
||||
return path.join(pelican.settings['PATH'],
|
||||
pelican.settings.get("IMAGE_PATH", DEFAULT_IMAGE_DIR)).rstrip('/')
|
||||
|
||||
|
||||
def expand_gallery(generator, metadata):
|
||||
""" Expand a gallery tag to include all of the files in a specific directory under IMAGE_PATH
|
||||
|
||||
:param pelican: The pelican instance
|
||||
:return: None
|
||||
"""
|
||||
if "gallery" not in metadata or metadata['gallery'] is None:
|
||||
return # If no gallery specified, we do nothing
|
||||
|
||||
lines = [ ]
|
||||
base_path = _image_path(generator)
|
||||
in_path = path.join(base_path, metadata['gallery'])
|
||||
template = generator.settings.get('GALLERY_TEMPLATE', DEFAULT_TEMPLATE)
|
||||
thumbnail_name = generator.settings.get("GALLERY_THUMBNAIL", DEFAULT_GALLERY_THUMB)
|
||||
thumbnail_prefix = generator.settings.get("")
|
||||
resizer = _resizer(thumbnail_name, '?x?', base_path)
|
||||
for dirpath, _, filenames in os.walk(in_path):
|
||||
for filename in filenames:
|
||||
if not filename.startswith('.'):
|
||||
url = path.join(dirpath, filename).replace(base_path, "")[1:]
|
||||
url = path.join('/static', generator.settings.get('IMAGE_PATH', DEFAULT_IMAGE_DIR), url).replace('\\', '/')
|
||||
logger.debug("GALLERY: {0}".format(url))
|
||||
thumbnail = resizer.get_thumbnail_name(filename)
|
||||
thumbnail = path.join('/', generator.settings.get('THUMBNAIL_DIR', DEFAULT_THUMBNAIL_DIR), thumbnail).replace('\\', '/')
|
||||
lines.append(template.format(
|
||||
filename=filename,
|
||||
url=url,
|
||||
thumbnail=thumbnail,
|
||||
))
|
||||
metadata['gallery_content'] = "\n".join(lines)
|
||||
|
||||
|
||||
def register():
|
||||
signals.finalized.connect(resize_thumbnails)
|
||||
signals.article_generator_context.connect(expand_gallery)
|