Compare commits

...

12 commits

Author SHA1 Message Date
Scott Boone (github signing key)
fa006ab3a4 provided better doc on how to run colorbox for #4 2015-09-02 18:47:54 -05:00
Scott Boone (github signing key)
f1d798bace add ROOT_ALBUMS to context, closes #20 2015-09-02 18:42:38 -05:00
Scott Boone (github signing key)
6ebca5751c Merge branch 'Spindel-master'
update to make this Python3 friendly
2015-09-02 18:16:50 -05:00
D.S. Ljungmark
b5c97b0f61 Fix the code to actually work on Python3
.keys() is an iterator ,and .iteritems() is items()
2015-08-21 18:04:24 +02:00
Scott Boone (github signing key)
1b47432d2c restored SIGLICAN_MAKE_THUMBS... for now 2014-10-17 17:27:02 -05:00
Scott Boone (github signing key)
cae0ac62f1 removed duplicative tumbnail creation that broke pelican workflow. fixes #17 2014-10-17 17:00:56 -05:00
Scott Boone (github signing key)
826e8567f5 removed settings debug spam 2014-10-17 16:25:17 -05:00
Scott Boone (github signing key)
b262f7949d get_exif now working, convoluted, fixes #15 2014-10-17 15:10:53 -05:00
Scott Boone (github signing key)
51021d726b better progress bar, implements #9 2014-10-17 13:17:04 -05:00
Scott Boone (github signing key)
0a4a4d6d9d clarification edits 2014-10-11 09:25:33 -05:00
Scott Boone (github signing key)
29b3ef6888 clarification edits 2014-10-11 09:24:18 -05:00
Scott Boone (github signing key)
2a5e6b9755 added license info for example images 2014-10-11 08:47:11 -05:00
9 changed files with 178 additions and 136 deletions

15
LICENSE
View file

@ -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.

View file

@ -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:
``` ```

View file

@ -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)

View file

@ -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>

View 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.

View 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 %}

View file

@ -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)

View file

@ -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
@ -92,9 +94,9 @@ 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)
@ -173,7 +175,6 @@ class SigalGalleryGenerator(Generator):
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:
@ -187,8 +188,14 @@ class SigalGalleryGenerator(Generator):
#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
@ -210,17 +217,23 @@ class SigalGalleryGenerator(Generator):
# 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,6 +243,8 @@ 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
@ -256,8 +271,10 @@ class SigalGalleryGenerator(Generator):
## - 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)

View file

@ -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)