|
@@ -1,13 +1,15 @@
|
|
# -*- coding: utf-8 -*-
|
|
# -*- coding: utf-8 -*-
|
|
from __future__ import unicode_literals
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
|
|
+import datetime
|
|
import itertools
|
|
import itertools
|
|
|
|
+import json
|
|
import logging
|
|
import logging
|
|
|
|
+import multiprocessing
|
|
import os
|
|
import os
|
|
import pprint
|
|
import pprint
|
|
import re
|
|
import re
|
|
import sys
|
|
import sys
|
|
-import multiprocessing
|
|
|
|
|
|
|
|
from pelican.generators import ArticlesGenerator
|
|
from pelican.generators import ArticlesGenerator
|
|
from pelican.generators import PagesGenerator
|
|
from pelican.generators import PagesGenerator
|
|
@@ -17,15 +19,22 @@ from pelican.utils import pelican_open
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
-
|
|
|
|
try:
|
|
try:
|
|
- from PIL import ExifTags
|
|
|
|
from PIL import Image
|
|
from PIL import Image
|
|
from PIL import ImageDraw
|
|
from PIL import ImageDraw
|
|
from PIL import ImageEnhance
|
|
from PIL import ImageEnhance
|
|
from PIL import ImageFont
|
|
from PIL import ImageFont
|
|
except ImportError:
|
|
except ImportError:
|
|
- raise Exception('PIL/Pillow not found')
|
|
|
|
|
|
+ logger.error('PIL/Pillow not found')
|
|
|
|
+
|
|
|
|
+try:
|
|
|
|
+ import piexif
|
|
|
|
+except ImportError:
|
|
|
|
+ ispiexif = False
|
|
|
|
+ logger.warning('piexif not found! Cannot use exif manipulation features')
|
|
|
|
+else:
|
|
|
|
+ ispiexif = True
|
|
|
|
+ logger.debug('piexif found.')
|
|
|
|
|
|
|
|
|
|
def initialized(pelican):
|
|
def initialized(pelican):
|
|
@@ -37,14 +46,23 @@ def initialized(pelican):
|
|
DEFAULT_CONFIG.setdefault('PHOTO_ARTICLE', (760, 506, 80))
|
|
DEFAULT_CONFIG.setdefault('PHOTO_ARTICLE', (760, 506, 80))
|
|
DEFAULT_CONFIG.setdefault('PHOTO_THUMB', (192, 144, 60))
|
|
DEFAULT_CONFIG.setdefault('PHOTO_THUMB', (192, 144, 60))
|
|
DEFAULT_CONFIG.setdefault('PHOTO_GALLERY_TITLE', '')
|
|
DEFAULT_CONFIG.setdefault('PHOTO_GALLERY_TITLE', '')
|
|
- DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK', 'False')
|
|
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_ALPHA_BACKGROUND_COLOR', (255, 255, 255))
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK', False)
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_THUMB', False)
|
|
DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_TEXT', DEFAULT_CONFIG['SITENAME'])
|
|
DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_TEXT', DEFAULT_CONFIG['SITENAME'])
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_TEXT_COLOR', (255, 255, 255))
|
|
DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_IMG', '')
|
|
DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_IMG', '')
|
|
- DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_IMG_SIZE', (64, 64))
|
|
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_WATERMARK_IMG_SIZE', False)
|
|
DEFAULT_CONFIG.setdefault('PHOTO_RESIZE_JOBS', 1)
|
|
DEFAULT_CONFIG.setdefault('PHOTO_RESIZE_JOBS', 1)
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_EXIF_KEEP', False)
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_EXIF_REMOVE_GPS', False)
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_EXIF_AUTOROTATE', True)
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_EXIF_COPYRIGHT', False)
|
|
|
|
+ DEFAULT_CONFIG.setdefault('PHOTO_EXIF_COPYRIGHT_AUTHOR', DEFAULT_CONFIG['SITENAME'])
|
|
|
|
|
|
DEFAULT_CONFIG['queue_resize'] = {}
|
|
DEFAULT_CONFIG['queue_resize'] = {}
|
|
DEFAULT_CONFIG['created_galleries'] = {}
|
|
DEFAULT_CONFIG['created_galleries'] = {}
|
|
|
|
+ DEFAULT_CONFIG['plugin_dir'] = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
|
|
if pelican:
|
|
if pelican:
|
|
pelican.settings.setdefault('PHOTO_LIBRARY', p)
|
|
pelican.settings.setdefault('PHOTO_LIBRARY', p)
|
|
@@ -52,11 +70,19 @@ def initialized(pelican):
|
|
pelican.settings.setdefault('PHOTO_ARTICLE', (760, 506, 80))
|
|
pelican.settings.setdefault('PHOTO_ARTICLE', (760, 506, 80))
|
|
pelican.settings.setdefault('PHOTO_THUMB', (192, 144, 60))
|
|
pelican.settings.setdefault('PHOTO_THUMB', (192, 144, 60))
|
|
pelican.settings.setdefault('PHOTO_GALLERY_TITLE', '')
|
|
pelican.settings.setdefault('PHOTO_GALLERY_TITLE', '')
|
|
- pelican.settings.setdefault('PHOTO_WATERMARK', 'False')
|
|
|
|
|
|
+ pelican.settings.setdefault('PHOTO_ALPHA_BACKGROUND_COLOR', (255, 255, 255))
|
|
|
|
+ pelican.settings.setdefault('PHOTO_WATERMARK', False)
|
|
|
|
+ pelican.settings.setdefault('PHOTO_WATERMARK_THUMB', False)
|
|
pelican.settings.setdefault('PHOTO_WATERMARK_TEXT', pelican.settings['SITENAME'])
|
|
pelican.settings.setdefault('PHOTO_WATERMARK_TEXT', pelican.settings['SITENAME'])
|
|
|
|
+ pelican.settings.setdefault('PHOTO_WATERMARK_TEXT_COLOR', (255, 255, 255))
|
|
pelican.settings.setdefault('PHOTO_WATERMARK_IMG', '')
|
|
pelican.settings.setdefault('PHOTO_WATERMARK_IMG', '')
|
|
- pelican.settings.setdefault('PHOTO_WATERMARK_IMG_SIZE', (64, 64))
|
|
|
|
|
|
+ pelican.settings.setdefault('PHOTO_WATERMARK_IMG_SIZE', False)
|
|
pelican.settings.setdefault('PHOTO_RESIZE_JOBS', 1)
|
|
pelican.settings.setdefault('PHOTO_RESIZE_JOBS', 1)
|
|
|
|
+ pelican.settings.setdefault('PHOTO_EXIF_KEEP', False)
|
|
|
|
+ pelican.settings.setdefault('PHOTO_EXIF_REMOVE_GPS', False)
|
|
|
|
+ pelican.settings.setdefault('PHOTO_EXIF_AUTOROTATE', True)
|
|
|
|
+ pelican.settings.setdefault('PHOTO_EXIF_COPYRIGHT', False)
|
|
|
|
+ pelican.settings.setdefault('PHOTO_EXIF_COPYRIGHT_AUTHOR', pelican.settings['AUTHOR'])
|
|
|
|
|
|
|
|
|
|
def read_notes(filename, msg=None):
|
|
def read_notes(filename, msg=None):
|
|
@@ -83,7 +109,6 @@ def read_notes(filename, msg=None):
|
|
|
|
|
|
|
|
|
|
def enqueue_resize(orig, resized, spec=(640, 480, 80)):
|
|
def enqueue_resize(orig, resized, spec=(640, 480, 80)):
|
|
-
|
|
|
|
if resized not in DEFAULT_CONFIG['queue_resize']:
|
|
if resized not in DEFAULT_CONFIG['queue_resize']:
|
|
DEFAULT_CONFIG['queue_resize'][resized] = (orig, spec)
|
|
DEFAULT_CONFIG['queue_resize'][resized] = (orig, spec)
|
|
elif DEFAULT_CONFIG['queue_resize'][resized] != (orig, spec):
|
|
elif DEFAULT_CONFIG['queue_resize'][resized] != (orig, spec):
|
|
@@ -94,13 +119,19 @@ def isalpha(img):
|
|
return True if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info) else False
|
|
return True if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info) else False
|
|
|
|
|
|
|
|
|
|
|
|
+def remove_alpha(img, bg_color):
|
|
|
|
+ background = Image.new("RGB", img.size, bg_color)
|
|
|
|
+ background.paste(img, mask=img.split()[3]) # 3 is the alpha channel
|
|
|
|
+
|
|
|
|
+ return background
|
|
|
|
+
|
|
|
|
+
|
|
def ReduceOpacity(im, opacity):
|
|
def ReduceOpacity(im, opacity):
|
|
- """Reduces Opacity
|
|
|
|
|
|
+ """Reduces Opacity.
|
|
|
|
|
|
Returns an image with reduced opacity.
|
|
Returns an image with reduced opacity.
|
|
Taken from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/362879
|
|
Taken from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/362879
|
|
"""
|
|
"""
|
|
-
|
|
|
|
assert opacity >= 0 and opacity <= 1
|
|
assert opacity >= 0 and opacity <= 1
|
|
if isalpha(im):
|
|
if isalpha(im):
|
|
im = im.copy()
|
|
im = im.copy()
|
|
@@ -113,7 +144,7 @@ def ReduceOpacity(im, opacity):
|
|
return im
|
|
return im
|
|
|
|
|
|
|
|
|
|
-def watermark_photo(image, watermark_text, watermark_image, watermark_image_size):
|
|
|
|
|
|
+def watermark_photo(image, settings):
|
|
|
|
|
|
margin = [10, 10]
|
|
margin = [10, 10]
|
|
opacity = 0.6
|
|
opacity = 0.6
|
|
@@ -124,22 +155,20 @@ def watermark_photo(image, watermark_text, watermark_image, watermark_image_size
|
|
image_reducer = 8
|
|
image_reducer = 8
|
|
text_size = [0, 0]
|
|
text_size = [0, 0]
|
|
mark_size = [0, 0]
|
|
mark_size = [0, 0]
|
|
- font_height = 0
|
|
|
|
text_position = [0, 0]
|
|
text_position = [0, 0]
|
|
|
|
|
|
- if watermark_text:
|
|
|
|
|
|
+ if settings['PHOTO_WATERMARK_TEXT']:
|
|
font_name = 'SourceCodePro-Bold.otf'
|
|
font_name = 'SourceCodePro-Bold.otf'
|
|
-
|
|
|
|
- plugin_dir = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
- default_font = os.path.join(plugin_dir, font_name)
|
|
|
|
|
|
+ default_font = os.path.join(DEFAULT_CONFIG['plugin_dir'], font_name)
|
|
font = ImageFont.FreeTypeFont(default_font, watermark_layer.size[0] // text_reducer)
|
|
font = ImageFont.FreeTypeFont(default_font, watermark_layer.size[0] // text_reducer)
|
|
- text_size = draw_watermark.textsize(watermark_text, font)
|
|
|
|
|
|
+ text_size = draw_watermark.textsize(settings['PHOTO_WATERMARK_TEXT'], font)
|
|
text_position = [image.size[i] - text_size[i] - margin[i] for i in [0, 1]]
|
|
text_position = [image.size[i] - text_size[i] - margin[i] for i in [0, 1]]
|
|
- draw_watermark.text(text_position, watermark_text, (255, 255, 255), font=font)
|
|
|
|
|
|
+ draw_watermark.text(text_position, settings['PHOTO_WATERMARK_TEXT'], settings['PHOTO_WATERMARK_TEXT_COLOR'], font=font)
|
|
|
|
|
|
- if watermark_image:
|
|
|
|
- mark_image = Image.open(watermark_image)
|
|
|
|
- mark_image_size = [ watermark_layer.size[0] // image_reducer for size in mark_size]
|
|
|
|
|
|
+ if settings['PHOTO_WATERMARK_IMG']:
|
|
|
|
+ mark_image = Image.open(settings['PHOTO_WATERMARK_IMG'])
|
|
|
|
+ mark_image_size = [watermark_layer.size[0] // image_reducer for size in mark_size]
|
|
|
|
+ mark_image_size = settings['PHOTO_WATERMARK_IMG_SIZE'] if settings['PHOTO_WATERMARK_IMG_SIZE'] else mark_image_size
|
|
mark_image.thumbnail(mark_image_size, Image.ANTIALIAS)
|
|
mark_image.thumbnail(mark_image_size, Image.ANTIALIAS)
|
|
mark_position = [watermark_layer.size[i] - mark_image.size[i] - margin[i] for i in [0, 1]]
|
|
mark_position = [watermark_layer.size[i] - mark_image.size[i] - margin[i] for i in [0, 1]]
|
|
mark_position = tuple([mark_position[0] - (text_size[0] // 2) + (mark_image_size[0] // 2), mark_position[1] - text_size[1]])
|
|
mark_position = tuple([mark_position[0] - (text_size[0] // 2) + (mark_image_size[0] // 2), mark_position[1] - text_size[1]])
|
|
@@ -155,56 +184,121 @@ def watermark_photo(image, watermark_text, watermark_image, watermark_image_size
|
|
return image
|
|
return image
|
|
|
|
|
|
|
|
|
|
-def resize_worker(orig, resized, spec, wm, wm_text, wm_img, wm_img_size):
|
|
|
|
|
|
+def rotate_image(img, exif_dict):
|
|
|
|
+
|
|
|
|
+ if "exif" in img.info and piexif.ImageIFD.Orientation in exif_dict["0th"]:
|
|
|
|
+ orientation = exif_dict["0th"].pop(piexif.ImageIFD.Orientation)
|
|
|
|
+ if orientation == 2:
|
|
|
|
+ img = img.transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
+ elif orientation == 3:
|
|
|
|
+ img = img.rotate(180)
|
|
|
|
+ elif orientation == 4:
|
|
|
|
+ img = img.rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
+ elif orientation == 5:
|
|
|
|
+ img = img.rotate(-90).transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
+ elif orientation == 6:
|
|
|
|
+ img = img.rotate(-90)
|
|
|
|
+ elif orientation == 7:
|
|
|
|
+ img = img.rotate(90).transpose(Image.FLIP_LEFT_RIGHT)
|
|
|
|
+ elif orientation == 8:
|
|
|
|
+ img = img.rotate(90)
|
|
|
|
+
|
|
|
|
+ return (img, exif_dict)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def build_license(license, author):
|
|
|
|
+
|
|
|
|
+ year = datetime.datetime.now().year
|
|
|
|
+ license_file = os.path.join(DEFAULT_CONFIG['plugin_dir'], 'licenses.json')
|
|
|
|
+
|
|
|
|
+ with open(license_file) as data_file:
|
|
|
|
+ licenses = json.load(data_file)
|
|
|
|
+
|
|
|
|
+ if any(license in k for k in licenses):
|
|
|
|
+ return licenses[license]['Text'].format(Author=author, Year=year, URL=licenses[license]['URL'])
|
|
|
|
+ else:
|
|
|
|
+ return 'Copyright {Year} {Author}, All Rights Reserved'.format(Author=author, Year=year)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def manipulate_exif(img, settings):
|
|
|
|
+
|
|
|
|
+ try:
|
|
|
|
+ exif = piexif.load(img.info['exif'])
|
|
|
|
+ except Exception:
|
|
|
|
+ logger.debug('EXIF information not found')
|
|
|
|
+ exif = {}
|
|
|
|
+
|
|
|
|
+ if settings['PHOTO_EXIF_AUTOROTATE']:
|
|
|
|
+ img, exif = rotate_image(img, exif)
|
|
|
|
+
|
|
|
|
+ if settings['PHOTO_EXIF_REMOVE_GPS']:
|
|
|
|
+ exif.pop('GPS')
|
|
|
|
+
|
|
|
|
+ if settings['PHOTO_EXIF_COPYRIGHT']:
|
|
|
|
+
|
|
|
|
+ # We want to be minimally destructive to any preset exif author or copyright information.
|
|
|
|
+ # If there is copyright or author information prefer that over everything else.
|
|
|
|
+ if not exif['0th'].get(piexif.ImageIFD.Artist):
|
|
|
|
+ exif['0th'][piexif.ImageIFD.Artist] = settings['PHOTO_EXIF_COPYRIGHT_AUTHOR']
|
|
|
|
+ author = settings['PHOTO_EXIF_COPYRIGHT_AUTHOR']
|
|
|
|
+
|
|
|
|
+ if not exif['0th'].get(piexif.ImageIFD.Copyright):
|
|
|
|
+ license = build_license(settings['PHOTO_EXIF_COPYRIGHT'], author)
|
|
|
|
+ exif['0th'][piexif.ImageIFD.Copyright] = license
|
|
|
|
+
|
|
|
|
+ return (img, piexif.dump(exif))
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def resize_worker(orig, resized, spec, settings):
|
|
|
|
+
|
|
logger.info('photos: make photo {} -> {}'.format(orig, resized))
|
|
logger.info('photos: make photo {} -> {}'.format(orig, resized))
|
|
im = Image.open(orig)
|
|
im = Image.open(orig)
|
|
- try:
|
|
|
|
- exif = im._getexif()
|
|
|
|
- except:
|
|
|
|
- exif = None
|
|
|
|
|
|
|
|
- icc_profile = im.info.get("icc_profile", None)
|
|
|
|
|
|
+ if ispiexif and settings['PHOTO_EXIF_KEEP'] and im.format == 'JPEG': # Only works with JPEG exif for sure.
|
|
|
|
+ im, exif_copy = manipulate_exif(im, settings)
|
|
|
|
+ else:
|
|
|
|
+ exif_copy = b''
|
|
|
|
|
|
- if exif:
|
|
|
|
- for tag, value in exif.items():
|
|
|
|
- decoded = ExifTags.TAGS.get(tag, tag)
|
|
|
|
- if decoded == 'Orientation':
|
|
|
|
- if value == 3:
|
|
|
|
- im = im.rotate(180)
|
|
|
|
- elif value == 6:
|
|
|
|
- im = im.rotate(270)
|
|
|
|
- elif value == 8:
|
|
|
|
- im = im.rotate(90)
|
|
|
|
- break
|
|
|
|
|
|
+ icc_profile = im.info.get("icc_profile", None)
|
|
im.thumbnail((spec[0], spec[1]), Image.ANTIALIAS)
|
|
im.thumbnail((spec[0], spec[1]), Image.ANTIALIAS)
|
|
- try:
|
|
|
|
- os.makedirs(os.path.split(resized)[0])
|
|
|
|
- except:
|
|
|
|
- pass
|
|
|
|
|
|
+ directory = os.path.split(resized)[0]
|
|
|
|
+
|
|
|
|
+ if isalpha(im):
|
|
|
|
+ im = remove_alpha(im, settings['PHOTO_ALPHA_BACKGROUND_COLOR'])
|
|
|
|
+
|
|
|
|
+ if not os.path.exists(directory):
|
|
|
|
+ try:
|
|
|
|
+ os.makedirs(directory)
|
|
|
|
+ except Exception:
|
|
|
|
+ logger.exception('Could not create {}'.format(directory))
|
|
|
|
+ else:
|
|
|
|
+ logger.debug('Directory already exists at {}'.format(os.path.split(resized)[0]))
|
|
|
|
|
|
- if wm:
|
|
|
|
- im = watermark_photo(im, wm_text, wm_img, wm_img_size)
|
|
|
|
|
|
+ if settings['PHOTO_WATERMARK']:
|
|
|
|
+ isthumb = True if spec == settings['PHOTO_THUMB'] else False
|
|
|
|
+ if not isthumb or (isthumb and settings['PHOTO_WATERMARK_THUMB']):
|
|
|
|
+ im = watermark_photo(im, settings)
|
|
|
|
|
|
- im.save(resized, 'JPEG', quality=spec[2], icc_profile=icc_profile)
|
|
|
|
|
|
+ im.save(resized, 'JPEG', quality=spec[2], icc_profile=icc_profile, exif=exif_copy)
|
|
|
|
|
|
|
|
|
|
def resize_photos(generator, writer):
|
|
def resize_photos(generator, writer):
|
|
- logger.info('photos: {} photo resizes to consider.'.format(len(DEFAULT_CONFIG['queue_resize'].items())))
|
|
|
|
|
|
+ if generator.settings['PHOTO_RESIZE_JOBS'] == -1:
|
|
|
|
+ debug = True
|
|
|
|
+ generator.settings['PHOTO_RESIZE_JOBS'] = 1
|
|
|
|
+ else:
|
|
|
|
+ debug = False
|
|
|
|
+
|
|
pool = multiprocessing.Pool(generator.settings['PHOTO_RESIZE_JOBS'])
|
|
pool = multiprocessing.Pool(generator.settings['PHOTO_RESIZE_JOBS'])
|
|
|
|
+ logger.debug('Debug Status: {}'.format(debug))
|
|
for resized, what in DEFAULT_CONFIG['queue_resize'].items():
|
|
for resized, what in DEFAULT_CONFIG['queue_resize'].items():
|
|
resized = os.path.join(generator.output_path, resized)
|
|
resized = os.path.join(generator.output_path, resized)
|
|
orig, spec = what
|
|
orig, spec = what
|
|
- if (not os.path.isfile(resized) or
|
|
|
|
- os.path.getmtime(orig) > os.path.getmtime(resized)):
|
|
|
|
- pool.apply_async(resize_worker, args=(
|
|
|
|
- orig,
|
|
|
|
- resized,
|
|
|
|
- spec,
|
|
|
|
- generator.settings['PHOTO_WATERMARK'],
|
|
|
|
- generator.settings['PHOTO_WATERMARK_TEXT'],
|
|
|
|
- generator.settings['PHOTO_WATERMARK_IMG'],
|
|
|
|
- generator.settings['PHOTO_WATERMARK_IMG_SIZE']
|
|
|
|
- ))
|
|
|
|
|
|
+ if (not os.path.isfile(resized) or os.path.getmtime(orig) > os.path.getmtime(resized)):
|
|
|
|
+ if debug:
|
|
|
|
+ resize_worker(orig, resized, spec, generator.settings)
|
|
|
|
+ else:
|
|
|
|
+ pool.apply_async(resize_worker, (orig, resized, spec, generator.settings))
|
|
|
|
|
|
pool.close()
|
|
pool.close()
|
|
pool.join()
|
|
pool.join()
|
|
@@ -257,7 +351,7 @@ def galleries_string_decompose(gallery_string):
|
|
title_regex = re.compile(r'{(.+)}')
|
|
title_regex = re.compile(r'{(.+)}')
|
|
galleries = map(unicode.strip if sys.version_info.major == 2 else str.strip, filter(None, splitter_regex.split(gallery_string)))
|
|
galleries = map(unicode.strip if sys.version_info.major == 2 else str.strip, filter(None, splitter_regex.split(gallery_string)))
|
|
galleries = [gallery[1:] if gallery.startswith('/') else gallery for gallery in galleries]
|
|
galleries = [gallery[1:] if gallery.startswith('/') else gallery for gallery in galleries]
|
|
- if len(galleries) % 2 == 0 and u' ' not in galleries:
|
|
|
|
|
|
+ if len(galleries) % 2 == 0 and ' ' not in galleries:
|
|
galleries = zip(zip(['type'] * len(galleries[0::2]), galleries[0::2]), zip(['location'] * len(galleries[0::2]), galleries[1::2]))
|
|
galleries = zip(zip(['type'] * len(galleries[0::2]), galleries[0::2]), zip(['location'] * len(galleries[0::2]), galleries[1::2]))
|
|
galleries = [dict(gallery) for gallery in galleries]
|
|
galleries = [dict(gallery) for gallery in galleries]
|
|
for gallery in galleries:
|
|
for gallery in galleries:
|
|
@@ -269,8 +363,7 @@ def galleries_string_decompose(gallery_string):
|
|
gallery['title'] = DEFAULT_CONFIG['PHOTO_GALLERY_TITLE']
|
|
gallery['title'] = DEFAULT_CONFIG['PHOTO_GALLERY_TITLE']
|
|
return galleries
|
|
return galleries
|
|
else:
|
|
else:
|
|
- logger.critical('Unexpected gallery location format! \n{}'.format(pprint.pformat(galleries)))
|
|
|
|
- raise Exception
|
|
|
|
|
|
+ logger.error('Unexpected gallery location format! \n{}'.format(pprint.pformat(galleries)))
|
|
|
|
|
|
|
|
|
|
def process_gallery(generator, content, location):
|
|
def process_gallery(generator, content, location):
|
|
@@ -333,8 +426,7 @@ def process_gallery(generator, content, location):
|
|
logger.debug('Gallery Data: '.format(pprint.pformat(content.photo_gallery)))
|
|
logger.debug('Gallery Data: '.format(pprint.pformat(content.photo_gallery)))
|
|
DEFAULT_CONFIG['created_galleries']['gallery'] = content_gallery
|
|
DEFAULT_CONFIG['created_galleries']['gallery'] = content_gallery
|
|
else:
|
|
else:
|
|
- logger.critical('photos: Gallery does not exist: {} at {}'.format(gallery['location'], dir_gallery))
|
|
|
|
- raise Exception
|
|
|
|
|
|
+ logger.error('photos: Gallery does not exist: {} at {}'.format(gallery['location'], dir_gallery))
|
|
|
|
|
|
|
|
|
|
def detect_gallery(generator, content):
|
|
def detect_gallery(generator, content):
|
|
@@ -346,10 +438,15 @@ def detect_gallery(generator, content):
|
|
logger.error('photos: Gallery tag not recognized: {}'.format(gallery))
|
|
logger.error('photos: Gallery tag not recognized: {}'.format(gallery))
|
|
|
|
|
|
|
|
|
|
-def process_image(generator, content, image):
|
|
|
|
|
|
+def image_clipper(x):
|
|
|
|
+ return x[8:] if x[8] == '/' else x[7:]
|
|
|
|
|
|
- image_clipper = lambda x: x[8:] if x[8] == '/' else x[7:]
|
|
|
|
- file_clipper = lambda x: x[11:] if x[10] == '/' else x[10:]
|
|
|
|
|
|
+
|
|
|
|
+def file_clipper(x):
|
|
|
|
+ return x[11:] if x[10] == '/' else x[10:]
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def process_image(generator, content, image):
|
|
|
|
|
|
if image.startswith('{photo}'):
|
|
if image.startswith('{photo}'):
|
|
path = os.path.join(os.path.expanduser(generator.settings['PHOTO_LIBRARY']), image_clipper(image))
|
|
path = os.path.join(os.path.expanduser(generator.settings['PHOTO_LIBRARY']), image_clipper(image))
|
|
@@ -378,16 +475,16 @@ def process_image(generator, content, image):
|
|
|
|
|
|
|
|
|
|
def detect_image(generator, content):
|
|
def detect_image(generator, content):
|
|
- image = content.metadata.get('image', None)
|
|
|
|
- if image:
|
|
|
|
- if image.startswith('{photo}') or image.startswith('{filename}'):
|
|
|
|
- process_image(generator, content, image)
|
|
|
|
- else:
|
|
|
|
- logger.error('photos: Image tag not recognized: {}'.format(image))
|
|
|
|
|
|
+ image = content.metadata.get('image', None)
|
|
|
|
+ if image:
|
|
|
|
+ if image.startswith('{photo}') or image.startswith('{filename}'):
|
|
|
|
+ process_image(generator, content, image)
|
|
|
|
+ else:
|
|
|
|
+ logger.error('photos: Image tag not recognized: {}'.format(image))
|
|
|
|
|
|
|
|
|
|
def detect_images_and_galleries(generators):
|
|
def detect_images_and_galleries(generators):
|
|
- """Runs generator on both pages and articles. """
|
|
|
|
|
|
+ """Runs generator on both pages and articles."""
|
|
for generator in generators:
|
|
for generator in generators:
|
|
if isinstance(generator, ArticlesGenerator):
|
|
if isinstance(generator, ArticlesGenerator):
|
|
for article in itertools.chain(generator.articles, generator.drafts):
|
|
for article in itertools.chain(generator.articles, generator.drafts):
|
|
@@ -400,7 +497,7 @@ def detect_images_and_galleries(generators):
|
|
|
|
|
|
|
|
|
|
def register():
|
|
def register():
|
|
- """Uses the new style of registration based on GitHub Pelican issue #314. """
|
|
|
|
|
|
+ """Uses the new style of registration based on GitHub Pelican issue #314."""
|
|
signals.initialized.connect(initialized)
|
|
signals.initialized.connect(initialized)
|
|
try:
|
|
try:
|
|
signals.content_object_init.connect(detect_content)
|
|
signals.content_object_init.connect(detect_content)
|