Compare commits
12 commits
Author | SHA1 | Date | |
---|---|---|---|
|
fa006ab3a4 | ||
|
f1d798bace | ||
|
6ebca5751c | ||
|
b5c97b0f61 | ||
|
1b47432d2c | ||
|
cae0ac62f1 | ||
|
826e8567f5 | ||
|
b262f7949d | ||
|
51021d726b | ||
|
0a4a4d6d9d | ||
|
29b3ef6888 | ||
|
2a5e6b9755 |
9 changed files with 178 additions and 136 deletions
15
LICENSE
15
LICENSE
|
@ -1,3 +1,5 @@
|
||||||
|
SOFTWARE
|
||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
Copyright (c) 2014 - Scott Boone
|
Copyright (c) 2014 - Scott Boone
|
||||||
|
|
||||||
|
@ -18,3 +20,16 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
IN THE SOFTWARE.
|
IN THE SOFTWARE.
|
||||||
|
|
||||||
|
===
|
||||||
|
|
||||||
|
IMAGES
|
||||||
|
|
||||||
|
All example images copyright (c) 2014 - Scott Boone
|
||||||
|
|
||||||
|
They are licensed under Creative Commons Attribution 4.0 International:
|
||||||
|
http://creativecommons.org/licenses/by/4.0/legalcode
|
||||||
|
|
||||||
|
This license allows others to distribute, remix, tweak, and build upon this
|
||||||
|
work, even commercially, as long as they provide credit for the original
|
||||||
|
creation.
|
|
@ -7,7 +7,8 @@ Colorbox/Galleria static site generator.
|
||||||
##How To
|
##How To
|
||||||
1. Add this package to your Pelican plugins directory.
|
1. Add this package to your Pelican plugins directory.
|
||||||
2. Add 'siglican' to PLUGINS in pelicanconf.py. Add SIGLICAN_ settings to
|
2. Add 'siglican' to PLUGINS in pelicanconf.py. Add SIGLICAN_ settings to
|
||||||
pelicanconf.py as desired.
|
pelicanconf.py as desired (see "Pelican Configuration Settings" below for
|
||||||
|
a complete list).
|
||||||
3. Create a *siglican* directory in your base directory, at the same level as
|
3. Create a *siglican* directory in your base directory, at the same level as
|
||||||
*content*.
|
*content*.
|
||||||
4. Drag gallery.md from examples to your pelican *pages* directory and edit it.
|
4. Drag gallery.md from examples to your pelican *pages* directory and edit it.
|
||||||
|
@ -21,7 +22,9 @@ Colorbox/Galleria static site generator.
|
||||||
That will give your siglican theme a way to inject gallery-specific css and
|
That will give your siglican theme a way to inject gallery-specific css and
|
||||||
javascript into your gallery pages.
|
javascript into your gallery pages.
|
||||||
8. Create an 'images' folder under 'siglican'. Add album folders along with
|
8. Create an 'images' folder under 'siglican'. Add album folders along with
|
||||||
images and metadata.
|
images and metadata. See *examples/images* for simple examples or the
|
||||||
|
[Sigal documentation](http://sigal.readthedocs.org/en/latest/) for more
|
||||||
|
details.
|
||||||
|
|
||||||
###Example directory excerpt:
|
###Example directory excerpt:
|
||||||
```
|
```
|
||||||
|
|
97
album.py
97
album.py
|
@ -33,9 +33,11 @@ import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from PIL import Image as PILImage
|
||||||
|
|
||||||
from .compat import strxfrm, UnicodeMixin, url_quote
|
from .compat import strxfrm, UnicodeMixin, url_quote
|
||||||
from .utils import read_markdown, url_from_path
|
from .utils import read_markdown, url_from_path
|
||||||
|
from .image import process_image, get_exif_tags
|
||||||
|
|
||||||
class Media(UnicodeMixin):
|
class Media(UnicodeMixin):
|
||||||
"""Base Class for media files.
|
"""Base Class for media files.
|
||||||
|
@ -82,22 +84,7 @@ class Media(UnicodeMixin):
|
||||||
@property
|
@property
|
||||||
def thumbnail(self):
|
def thumbnail(self):
|
||||||
"""Path to the thumbnail image (relative to the album directory)."""
|
"""Path to the thumbnail image (relative to the album directory)."""
|
||||||
|
# cleanup: make this deal better with SIGLICAN_MAKE_THUMBS: False
|
||||||
if not os.path.isfile(self.thumb_path):
|
|
||||||
# if thumbnail is missing (if settings['make_thumbs'] is False)
|
|
||||||
if self.type == 'image':
|
|
||||||
generator = image.generate_thumbnail
|
|
||||||
elif self.type == 'video':
|
|
||||||
generator = video.generate_thumbnail
|
|
||||||
|
|
||||||
self.logger.debug('siglican: Generating thumbnail for %r', self)
|
|
||||||
try:
|
|
||||||
generator(self.src_path, self.thumb_path,
|
|
||||||
self.settings['SIGLICAN_THUMB_SIZE'],
|
|
||||||
fit=self.settings['SIGLICAN_THUMB_FIT'])
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error('siglican: Failed to generate thumbnail: %s', e)
|
|
||||||
return
|
|
||||||
return url_from_path(self.thumb_name)
|
return url_from_path(self.thumb_name)
|
||||||
|
|
||||||
def _get_metadata(self):
|
def _get_metadata(self):
|
||||||
|
@ -410,81 +397,3 @@ def get_thumb(settings, filename):
|
||||||
ext = '.jpg'
|
ext = '.jpg'
|
||||||
return os.path.join(path, settings['SIGLICAN_THUMB_DIR'], settings['SIGLICAN_THUMB_PREFIX'] +
|
return os.path.join(path, settings['SIGLICAN_THUMB_DIR'], settings['SIGLICAN_THUMB_PREFIX'] +
|
||||||
name + settings['SIGLICAN_THUMB_SUFFIX'] + ext)
|
name + settings['SIGLICAN_THUMB_SUFFIX'] + ext)
|
||||||
|
|
||||||
def get_exif_tags(source):
|
|
||||||
"""Read EXIF tags from file @source and return a tuple of two dictionaries,
|
|
||||||
the first one containing the raw EXIF data, the second one a simplified
|
|
||||||
version with common tags.
|
|
||||||
"""
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
if os.path.splitext(source)[1].lower() not in ('.jpg', '.jpeg'):
|
|
||||||
return (None, None)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = _get_exif_data(source)
|
|
||||||
except (IOError, IndexError, TypeError, AttributeError):
|
|
||||||
logger.warning(u'Could not read EXIF data from %s', source)
|
|
||||||
return (None, None)
|
|
||||||
|
|
||||||
simple = {}
|
|
||||||
|
|
||||||
# Provide more accessible tags in the 'simple' key
|
|
||||||
if 'FNumber' in data:
|
|
||||||
fnumber = data['FNumber']
|
|
||||||
simple['fstop'] = float(fnumber[0]) / fnumber[1]
|
|
||||||
|
|
||||||
if 'FocalLength' in data:
|
|
||||||
focal = data['FocalLength']
|
|
||||||
simple['focal'] = round(float(focal[0]) / focal[1])
|
|
||||||
|
|
||||||
if 'ExposureTime' in data:
|
|
||||||
if isinstance(data['ExposureTime'], tuple):
|
|
||||||
simple['exposure'] = '{0}/{1}'.format(*data['ExposureTime'])
|
|
||||||
elif isinstance(data['ExposureTime'], int):
|
|
||||||
simple['exposure'] = str(data['ExposureTime'])
|
|
||||||
else:
|
|
||||||
logger.warning('Unknown format for ExposureTime: %r (%s)',
|
|
||||||
data['ExposureTime'], source)
|
|
||||||
|
|
||||||
if 'ISOSpeedRatings' in data:
|
|
||||||
simple['iso'] = data['ISOSpeedRatings']
|
|
||||||
|
|
||||||
if 'DateTimeOriginal' in data:
|
|
||||||
try:
|
|
||||||
# Remove null bytes at the end if necessary
|
|
||||||
date = data['DateTimeOriginal'].rsplit('\x00')[0]
|
|
||||||
simple['dateobj'] = datetime.strptime(date, '%Y:%m:%d %H:%M:%S')
|
|
||||||
dt = simple['dateobj'].strftime('%A, %d. %B %Y')
|
|
||||||
|
|
||||||
if compat.PY2:
|
|
||||||
simple['datetime'] = dt.decode('utf8')
|
|
||||||
else:
|
|
||||||
simple['datetime'] = dt
|
|
||||||
except (ValueError, TypeError) as e:
|
|
||||||
logger.warning(u'Could not parse DateTimeOriginal of %s: %s',
|
|
||||||
source, e)
|
|
||||||
|
|
||||||
if 'GPSInfo' in data:
|
|
||||||
info = data['GPSInfo']
|
|
||||||
lat_info = info.get('GPSLatitude')
|
|
||||||
lon_info = info.get('GPSLongitude')
|
|
||||||
lat_ref_info = info.get('GPSLatitudeRef')
|
|
||||||
lon_ref_info = info.get('GPSLongitudeRef')
|
|
||||||
|
|
||||||
if lat_info and lon_info and lat_ref_info and lon_ref_info:
|
|
||||||
try:
|
|
||||||
lat = dms_to_degrees(lat_info)
|
|
||||||
lon = dms_to_degrees(lon_info)
|
|
||||||
except (ZeroDivisionError, ValueError):
|
|
||||||
logger.warning('Failed to read GPS info for %s', source)
|
|
||||||
lat = lon = None
|
|
||||||
|
|
||||||
if lat and lon:
|
|
||||||
simple['gps'] = {
|
|
||||||
'lat': - lat if lat_ref_info != 'N' else lat,
|
|
||||||
'lon': - lon if lon_ref_info != 'E' else lon,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (data, simple)
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>albums</h2>
|
<h2>albums</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{% for aname,album in ALBUMS.iteritems() %}
|
{% for aname,album in ALBUMS.items() %}
|
||||||
<li>{{ aname }}<a href="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.url }}"><img src="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.thumbnail }}"></a>
|
<li>{{ aname }}<a href="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.url }}"><img src="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.thumbnail }}"></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
3
examples/themes/colorbox/templates/README
Normal file
3
examples/themes/colorbox/templates/README
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
To make this play nicely with limited modification, move siglican_gallery.html
|
||||||
|
to your Pelican template. This will provide an entry point to colorbox which is
|
||||||
|
driven by album.html here.
|
17
examples/themes/colorbox/templates/siglican_gallery.html
Normal file
17
examples/themes/colorbox/templates/siglican_gallery.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>albums</h2>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
{% for aname,album in ROOT_ALBUMS.iteritems() %}
|
||||||
|
{% if loop.last %}
|
||||||
|
<div class="four columns end" style="text-align:right;">
|
||||||
|
{% else %}
|
||||||
|
<div class="four columns" style="text-align:right;">
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.url }}"><img src="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.thu
|
||||||
|
mbnail }}"><br>{{ aname }}</a><p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
82
image.py
82
image.py
|
@ -48,7 +48,6 @@ from pilkit.processors import Transpose
|
||||||
from pilkit.utils import save_image
|
from pilkit.utils import save_image
|
||||||
|
|
||||||
from . import compat #, signals
|
from . import compat #, signals
|
||||||
from .album import Video
|
|
||||||
|
|
||||||
def _has_exif_tags(img):
|
def _has_exif_tags(img):
|
||||||
return hasattr(img, 'info') and 'exif' in img.info
|
return hasattr(img, 'info') and 'exif' in img.info
|
||||||
|
@ -177,6 +176,84 @@ def dms_to_degrees(v):
|
||||||
s = float(v[2][0]) / float(v[2][1])
|
s = float(v[2][0]) / float(v[2][1])
|
||||||
return d + (m / 60.0) + (s / 3600.0)
|
return d + (m / 60.0) + (s / 3600.0)
|
||||||
|
|
||||||
|
def get_exif_tags(source):
|
||||||
|
"""Read EXIF tags from file @source and return a tuple of two dictionaries,
|
||||||
|
the first one containing the raw EXIF data, the second one a simplified
|
||||||
|
version with common tags.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if os.path.splitext(source)[1].lower() not in ('.jpg', '.jpeg'):
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = _get_exif_data(source)
|
||||||
|
except (IOError, IndexError, TypeError, AttributeError):
|
||||||
|
logger.warning(u'Could not read EXIF data from %s', source)
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
simple = {}
|
||||||
|
|
||||||
|
# Provide more accessible tags in the 'simple' key
|
||||||
|
if 'FNumber' in data:
|
||||||
|
fnumber = data['FNumber']
|
||||||
|
simple['fstop'] = float(fnumber[0]) / fnumber[1]
|
||||||
|
|
||||||
|
if 'FocalLength' in data:
|
||||||
|
focal = data['FocalLength']
|
||||||
|
simple['focal'] = round(float(focal[0]) / focal[1])
|
||||||
|
|
||||||
|
if 'ExposureTime' in data:
|
||||||
|
if isinstance(data['ExposureTime'], tuple):
|
||||||
|
simple['exposure'] = '{0}/{1}'.format(*data['ExposureTime'])
|
||||||
|
elif isinstance(data['ExposureTime'], int):
|
||||||
|
simple['exposure'] = str(data['ExposureTime'])
|
||||||
|
else:
|
||||||
|
logger.warning('Unknown format for ExposureTime: %r (%s)',
|
||||||
|
data['ExposureTime'], source)
|
||||||
|
|
||||||
|
if 'ISOSpeedRatings' in data:
|
||||||
|
simple['iso'] = data['ISOSpeedRatings']
|
||||||
|
|
||||||
|
if 'DateTimeOriginal' in data:
|
||||||
|
try:
|
||||||
|
# Remove null bytes at the end if necessary
|
||||||
|
date = data['DateTimeOriginal'].rsplit('\x00')[0]
|
||||||
|
simple['dateobj'] = datetime.strptime(date, '%Y:%m:%d %H:%M:%S')
|
||||||
|
dt = simple['dateobj'].strftime('%A, %d. %B %Y')
|
||||||
|
|
||||||
|
if compat.PY2:
|
||||||
|
simple['datetime'] = dt.decode('utf8')
|
||||||
|
else:
|
||||||
|
simple['datetime'] = dt
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
logger.warning(u'Could not parse DateTimeOriginal of %s: %s',
|
||||||
|
source, e)
|
||||||
|
|
||||||
|
if 'GPSInfo' in data:
|
||||||
|
info = data['GPSInfo']
|
||||||
|
lat_info = info.get('GPSLatitude')
|
||||||
|
lon_info = info.get('GPSLongitude')
|
||||||
|
lat_ref_info = info.get('GPSLatitudeRef')
|
||||||
|
lon_ref_info = info.get('GPSLongitudeRef')
|
||||||
|
|
||||||
|
if lat_info and lon_info and lat_ref_info and lon_ref_info:
|
||||||
|
try:
|
||||||
|
lat = dms_to_degrees(lat_info)
|
||||||
|
lon = dms_to_degrees(lon_info)
|
||||||
|
except (ZeroDivisionError, ValueError):
|
||||||
|
logger.warning('Failed to read GPS info for %s', source)
|
||||||
|
lat = lon = None
|
||||||
|
|
||||||
|
if lat and lon:
|
||||||
|
simple['gps'] = {
|
||||||
|
'lat': - lat if lat_ref_info != 'N' else lat,
|
||||||
|
'lon': - lon if lon_ref_info != 'E' else lon,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (data, simple)
|
||||||
|
|
||||||
def get_thumb(settings, filename):
|
def get_thumb(settings, filename):
|
||||||
"""Return the path to the thumb.
|
"""Return the path to the thumb.
|
||||||
|
|
||||||
|
@ -195,7 +272,8 @@ def get_thumb(settings, filename):
|
||||||
path, filen = os.path.split(filename)
|
path, filen = os.path.split(filename)
|
||||||
name, ext = os.path.splitext(filen)
|
name, ext = os.path.splitext(filen)
|
||||||
|
|
||||||
if ext.lower() in Video.extensions:
|
# TODO: replace this list with Video.extensions github #16
|
||||||
|
if ext.lower() in ('.mov', '.avi', '.mp4', '.webm', '.ogv'):
|
||||||
ext = '.jpg'
|
ext = '.jpg'
|
||||||
return os.path.join(path, settings['SIGLICAN_THUMB_DIR'], settings['SIGLICAN_THUMB_PREFIX'] +
|
return os.path.join(path, settings['SIGLICAN_THUMB_DIR'], settings['SIGLICAN_THUMB_PREFIX'] +
|
||||||
name + settings['SIGLICAN_THUMB_SUFFIX'] + ext)
|
name + settings['SIGLICAN_THUMB_SUFFIX'] + ext)
|
||||||
|
|
87
siglican.py
87
siglican.py
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2014 - Scott Boone
|
# Copyright (c) 2014 - Scott Boone
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
# in the Software without restriction, including without limitation the rights
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# The above copyright notice and this permission notice shall be included in
|
||||||
# all copies or substantial portions of the Software.
|
# all copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
@ -20,6 +20,8 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import locale
|
import locale
|
||||||
|
@ -35,7 +37,7 @@ from .writer import Writer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Default config from Sigal's settings module. These have been changed to
|
# Default config from Sigal's settings module. These have been changed to
|
||||||
# upper case because Pelican does not recognize lower case configuration names.
|
# upper case because Pelican does not recognize lower case configuration names.
|
||||||
# note: if a default is changed, please also update README.md
|
# note: if a default is changed, please also update README.md
|
||||||
_DEFAULT_SIGLICAN_SETTINGS = {
|
_DEFAULT_SIGLICAN_SETTINGS = {
|
||||||
|
@ -81,9 +83,9 @@ class SigalGalleryGenerator(Generator):
|
||||||
# reference: methods provided by Pelican Generator:
|
# reference: methods provided by Pelican Generator:
|
||||||
# def _update_context(self, items): adds more items to the context dict
|
# def _update_context(self, items): adds more items to the context dict
|
||||||
# def get_template(self, name): returns templates from theme based on theme
|
# def get_template(self, name): returns templates from theme based on theme
|
||||||
# def get_files(self, paths, exclude=[], extensions=None): paths to search,
|
# def get_files(self, paths, exclude=[], extensions=None): paths to search,
|
||||||
# exclude, allowed extensions
|
# exclude, allowed extensions
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize gallery dict and load in custom Sigal settings."""
|
"""Initialize gallery dict and load in custom Sigal settings."""
|
||||||
|
|
||||||
|
@ -92,27 +94,27 @@ class SigalGalleryGenerator(Generator):
|
||||||
# this needs to be first to establish pelican settings:
|
# this needs to be first to establish pelican settings:
|
||||||
super(SigalGalleryGenerator, self).__init__(*args, **kwargs)
|
super(SigalGalleryGenerator, self).__init__(*args, **kwargs)
|
||||||
# add default sigal settings to generator settings:
|
# add default sigal settings to generator settings:
|
||||||
for k in _DEFAULT_SIGLICAN_SETTINGS.keys()[:]:
|
for k in list(_DEFAULT_SIGLICAN_SETTINGS.keys()):
|
||||||
self.settings[k] = self.settings.get(k, _DEFAULT_SIGLICAN_SETTINGS[k])
|
self.settings[k] = self.settings.get(k, _DEFAULT_SIGLICAN_SETTINGS[k])
|
||||||
logger.debug("sigal.pelican: setting %s: %s",k,self.settings[k])
|
#logger.debug("sigal.pelican: setting %s: %s",k,self.settings[k])
|
||||||
self._clean_settings()
|
self._clean_settings()
|
||||||
# this is where we could create a signal if we wanted to, e.g.:
|
# this is where we could create a signal if we wanted to, e.g.:
|
||||||
# signals.gallery_generator_init.send(self)
|
# signals.gallery_generator_init.send(self)
|
||||||
|
|
||||||
def _clean_settings(self):
|
def _clean_settings(self):
|
||||||
"""Checks existence of directories and normalizes image size settings."""
|
"""Checks existence of directories and normalizes image size settings."""
|
||||||
|
|
||||||
# create absolute paths to source, theme and destination directories:
|
# create absolute paths to source, theme and destination directories:
|
||||||
init_source = self.settings['SIGLICAN_SOURCE']
|
init_source = self.settings['SIGLICAN_SOURCE']
|
||||||
self.settings['SIGLICAN_SOURCE'] = os.path.normpath(self.settings['PATH'] +
|
self.settings['SIGLICAN_SOURCE'] = os.path.normpath(self.settings['PATH'] +
|
||||||
"/../" + self.settings['SIGLICAN_SOURCE'] + '/images')
|
"/../" + self.settings['SIGLICAN_SOURCE'] + '/images')
|
||||||
self.settings['SIGLICAN_THEME'] = os.path.normpath(self.settings['PATH'] +
|
self.settings['SIGLICAN_THEME'] = os.path.normpath(self.settings['PATH'] +
|
||||||
"/../" + init_source + "/" + self.settings['SIGLICAN_THEME'])
|
"/../" + init_source + "/" + self.settings['SIGLICAN_THEME'])
|
||||||
self.settings['SIGLICAN_DESTINATION'] = os.path.normpath(
|
self.settings['SIGLICAN_DESTINATION'] = os.path.normpath(
|
||||||
self.settings['OUTPUT_PATH'] + "/" + self.settings['SIGLICAN_DESTINATION'])
|
self.settings['OUTPUT_PATH'] + "/" + self.settings['SIGLICAN_DESTINATION'])
|
||||||
|
|
||||||
enc = locale.getpreferredencoding() if PY2 else None
|
enc = locale.getpreferredencoding() if PY2 else None
|
||||||
|
|
||||||
# test for existence of source directories
|
# test for existence of source directories
|
||||||
pathkeys = ['SIGLICAN_SOURCE', 'SIGLICAN_THEME']
|
pathkeys = ['SIGLICAN_SOURCE', 'SIGLICAN_THEME']
|
||||||
for k in pathkeys:
|
for k in pathkeys:
|
||||||
|
@ -125,7 +127,7 @@ class SigalGalleryGenerator(Generator):
|
||||||
logger.error("siglican: missing source directory %s: %s",
|
logger.error("siglican: missing source directory %s: %s",
|
||||||
k,self.settings[k])
|
k,self.settings[k])
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# normalize sizes as e landscape
|
# normalize sizes as e landscape
|
||||||
for key in ('SIGLICAN_IMG_SIZE', 'SIGLICAN_THUMB_SIZE', 'SIGLICAN_VIDEO_SIZE'):
|
for key in ('SIGLICAN_IMG_SIZE', 'SIGLICAN_THUMB_SIZE', 'SIGLICAN_VIDEO_SIZE'):
|
||||||
w, h = self.settings[key]
|
w, h = self.settings[key]
|
||||||
|
@ -133,11 +135,11 @@ class SigalGalleryGenerator(Generator):
|
||||||
self.settings[key] = (h, w)
|
self.settings[key] = (h, w)
|
||||||
logger.warning("siglican: The %s setting should be specified "
|
logger.warning("siglican: The %s setting should be specified "
|
||||||
"with the largest value first.", key)
|
"with the largest value first.", key)
|
||||||
|
|
||||||
if not self.settings['SIGLICAN_IMG_PROCESSOR']:
|
if not self.settings['SIGLICAN_IMG_PROCESSOR']:
|
||||||
logger.info('No Processor, images will not be resized')
|
logger.info('No Processor, images will not be resized')
|
||||||
|
|
||||||
# based on Sigal's Gallery.__init__() method:
|
# based on Sigal's Gallery.__init__() method:
|
||||||
def generate_context(self):
|
def generate_context(self):
|
||||||
""""Update the global Pelican context that's shared between generators."""
|
""""Update the global Pelican context that's shared between generators."""
|
||||||
|
|
||||||
|
@ -172,55 +174,66 @@ class SigalGalleryGenerator(Generator):
|
||||||
for d in dirs[:]:
|
for d in dirs[:]:
|
||||||
path = os.path.join(relpath, d) if relpath != '.' else d
|
path = os.path.join(relpath, d) if relpath != '.' else d
|
||||||
if path not in self.albums.keys():
|
if path not in self.albums.keys():
|
||||||
dirs.remove(d)
|
dirs.remove(d)
|
||||||
|
|
||||||
album = Album(relpath, self.settings, dirs, files, self)
|
album = Album(relpath, self.settings, dirs, files, self)
|
||||||
|
|
||||||
if not album.medias and not album.albums:
|
if not album.medias and not album.albums:
|
||||||
logger.info('siglican: Skip empty album: %r', album)
|
logger.info('siglican: Skip empty album: %r', album)
|
||||||
else:
|
else:
|
||||||
self.albums[relpath] = album
|
self.albums[relpath] = album
|
||||||
# done generating context (self.albums) now
|
# done generating context (self.albums) now
|
||||||
logger.debug('siglican: albums:\n%r', self.albums.values())
|
logger.debug('siglican: albums:\n%r', self.albums.values())
|
||||||
|
|
||||||
# update the jinja context so that templates can access it:
|
# update the jinja context so that templates can access it:
|
||||||
#self._update_context(('albums', )) # unnecessary? **
|
#self._update_context(('albums', )) # unnecessary? **
|
||||||
self.context['ALBUMS'] = self.albums # ** change to SIGLICAN_ALBUMS?
|
self.context['ALBUMS'] = self.albums # ** change to SIGLICAN_ALBUMS?
|
||||||
|
|
||||||
|
root_albums = {}
|
||||||
|
for k,v in self.albums.items():
|
||||||
|
if os.sep not in v.path:
|
||||||
|
root_albums[k] = v
|
||||||
|
self.context['ROOT_ALBUMS'] = root_albums
|
||||||
|
|
||||||
# update the jinja context with the default sigal settings:
|
# update the jinja context with the default sigal settings:
|
||||||
for k,v in _DEFAULT_SIGLICAN_SETTINGS.iteritems():
|
for k,v in _DEFAULT_SIGLICAN_SETTINGS.items():
|
||||||
if not k in self.context:
|
if not k in self.context:
|
||||||
self.context[k] = v
|
self.context[k] = v
|
||||||
|
|
||||||
def generate_output(self, writer):
|
def generate_output(self, writer):
|
||||||
""" Creates gallery destination directories, thumbnails, resized
|
""" Creates gallery destination directories, thumbnails, resized
|
||||||
images, and moves everything into the destination."""
|
images, and moves everything into the destination."""
|
||||||
|
|
||||||
# note: ignore the writer sent by Pelican because it's not certain
|
# note: ignore the writer sent by Pelican because it's not certain
|
||||||
# which Writer it will send. if another active plugin also implements
|
# which Writer it will send. if another active plugin also implements
|
||||||
# Writer, Pelican may send that instead of one of its core Writers.
|
# Writer, Pelican may send that instead of one of its core Writers.
|
||||||
# I logged a feature request here:
|
# I logged a feature request here:
|
||||||
# https://github.com/getpelican/pelican/issues/1459
|
# https://github.com/getpelican/pelican/issues/1459
|
||||||
|
|
||||||
# create destination directory
|
# create destination directory
|
||||||
if not os.path.isdir(self.settings['SIGLICAN_DESTINATION']):
|
if not os.path.isdir(self.settings['SIGLICAN_DESTINATION']):
|
||||||
os.makedirs(self.settings['SIGLICAN_DESTINATION'])
|
os.makedirs(self.settings['SIGLICAN_DESTINATION'])
|
||||||
|
|
||||||
# github7 ** improve exception catching
|
# github7 ** improve exception catching
|
||||||
# github8 ** re-integrate multiprocessing logic from Sigal
|
# github8 ** re-integrate multiprocessing logic from Sigal
|
||||||
|
|
||||||
# generate thumbnails, process images, and move them to the destination
|
# generate thumbnails, process images, and move them to the destination
|
||||||
|
if logger.getEffectiveLevel() > logging.INFO:
|
||||||
|
print('siglican is processing media: ', end='')
|
||||||
|
sys.stdout.flush()
|
||||||
albums = self.albums
|
albums = self.albums
|
||||||
for a in albums:
|
for a in albums:
|
||||||
logger.debug("siglican: creating directory for %s",a)
|
logger.info("siglican: processing album: %s",a)
|
||||||
albums[a].create_output_directories()
|
albums[a].create_output_directories()
|
||||||
for media in albums[a].medias:
|
for media in albums[a].medias:
|
||||||
logger.debug("siglican: processing %r , source: %s, dst: %s",
|
if logger.getEffectiveLevel() > logging.INFO:
|
||||||
media,media.src_path,media.dst_path)
|
print('.', end='')
|
||||||
|
sys.stdout.flush()
|
||||||
if os.path.isfile(media.dst_path):
|
if os.path.isfile(media.dst_path):
|
||||||
logger.info("siglican: %s exists - skipping", media.filename)
|
logger.info("siglican: %s exists - skipping", media.filename)
|
||||||
self.stats[media.type + '_skipped'] += 1
|
self.stats[media.type + '_skipped'] += 1
|
||||||
else:
|
else:
|
||||||
|
logger.info("siglican: processing %r , source: %s, dst: %s",
|
||||||
|
media,media.src_path,media.dst_path)
|
||||||
self.stats[media.type] += 1
|
self.stats[media.type] += 1
|
||||||
logger.debug("MEDIA TYPE: %s",media.type)
|
logger.debug("MEDIA TYPE: %s",media.type)
|
||||||
# create/move resized images and thumbnails to output dirs:
|
# create/move resized images and thumbnails to output dirs:
|
||||||
|
@ -230,7 +243,9 @@ class SigalGalleryGenerator(Generator):
|
||||||
elif media.type == 'video':
|
elif media.type == 'video':
|
||||||
process_video(media.src_path,os.path.dirname(
|
process_video(media.src_path,os.path.dirname(
|
||||||
media.dst_path),self.settings)
|
media.dst_path),self.settings)
|
||||||
|
if logger.getEffectiveLevel() > logging.INFO:
|
||||||
|
print('')
|
||||||
|
|
||||||
# generate the index.html files for the albums
|
# generate the index.html files for the albums
|
||||||
if self.settings['SIGLICAN_WRITE_HTML']: # defaults to True
|
if self.settings['SIGLICAN_WRITE_HTML']: # defaults to True
|
||||||
# locate the theme; check for a custom theme in ./sigal/themes, if not
|
# locate the theme; check for a custom theme in ./sigal/themes, if not
|
||||||
|
@ -245,19 +260,21 @@ class SigalGalleryGenerator(Generator):
|
||||||
if not os.path.exists(self.theme):
|
if not os.path.exists(self.theme):
|
||||||
raise Exception("siglican: unable to find theme: %s" %
|
raise Exception("siglican: unable to find theme: %s" %
|
||||||
os.path.basename(self.theme))
|
os.path.basename(self.theme))
|
||||||
|
|
||||||
logger.info("siglican theme: %s", self.theme)
|
logger.info("siglican theme: %s", self.theme)
|
||||||
|
|
||||||
self.writer = Writer(self.context, self.theme, 'album')
|
self.writer = Writer(self.context, self.theme, 'album')
|
||||||
for album in self.albums.values():
|
for album in self.albums.values():
|
||||||
self.writer.write(album)
|
self.writer.write(album)
|
||||||
|
|
||||||
## possible cleanup:
|
## possible cleanup:
|
||||||
## - bring back Writer options that Sigal had?
|
## - bring back Writer options that Sigal had?
|
||||||
## - make sure thumbnails don't break in some cases [fixed?]
|
## - make sure thumbnails don't break in some cases [fixed?]
|
||||||
|
|
||||||
|
|
||||||
def get_generators(generators):
|
def get_generators(generators):
|
||||||
return SigalGalleryGenerator
|
return SigalGalleryGenerator
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
signals.get_generators.connect(get_generators)
|
signals.get_generators.connect(get_generators)
|
||||||
|
|
4
video.py
4
video.py
|
@ -30,7 +30,6 @@ import shutil
|
||||||
from os.path import splitext
|
from os.path import splitext
|
||||||
|
|
||||||
from . import image
|
from . import image
|
||||||
from .album import Video
|
|
||||||
from .utils import call_subprocess
|
from .utils import call_subprocess
|
||||||
|
|
||||||
# TODO: merge with image.py
|
# TODO: merge with image.py
|
||||||
|
@ -170,7 +169,8 @@ def get_thumb(settings, filename):
|
||||||
path, filen = os.path.split(filename)
|
path, filen = os.path.split(filename)
|
||||||
name, ext = os.path.splitext(filen)
|
name, ext = os.path.splitext(filen)
|
||||||
|
|
||||||
if ext.lower() in Video.extensions:
|
# TODO: replace this list with Video.extensions github #16
|
||||||
|
if ext.lower() in ('.mov', '.avi', '.mp4', '.webm', '.ogv'):
|
||||||
ext = '.jpg'
|
ext = '.jpg'
|
||||||
return os.path.join(path, settings['thumb_dir'], settings['thumb_prefix'] +
|
return os.path.join(path, settings['thumb_dir'], settings['thumb_prefix'] +
|
||||||
name + settings['thumb_suffix'] + ext)
|
name + settings['thumb_suffix'] + ext)
|
||||||
|
|
Loading…
Reference in a new issue