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)
|
||||
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
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
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
|
||||
1. Add this package to your Pelican plugins directory.
|
||||
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
|
||||
*content*.
|
||||
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
|
||||
javascript into your gallery pages.
|
||||
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:
|
||||
```
|
||||
|
|
97
album.py
97
album.py
|
@ -33,9 +33,11 @@ import os
|
|||
import logging
|
||||
|
||||
from collections import defaultdict
|
||||
from PIL import Image as PILImage
|
||||
|
||||
from .compat import strxfrm, UnicodeMixin, url_quote
|
||||
from .utils import read_markdown, url_from_path
|
||||
from .image import process_image, get_exif_tags
|
||||
|
||||
class Media(UnicodeMixin):
|
||||
"""Base Class for media files.
|
||||
|
@ -82,22 +84,7 @@ class Media(UnicodeMixin):
|
|||
@property
|
||||
def thumbnail(self):
|
||||
"""Path to the thumbnail image (relative to the album directory)."""
|
||||
|
||||
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
|
||||
# cleanup: make this deal better with SIGLICAN_MAKE_THUMBS: False
|
||||
return url_from_path(self.thumb_name)
|
||||
|
||||
def _get_metadata(self):
|
||||
|
@ -410,81 +397,3 @@ def get_thumb(settings, filename):
|
|||
ext = '.jpg'
|
||||
return os.path.join(path, settings['SIGLICAN_THUMB_DIR'], settings['SIGLICAN_THUMB_PREFIX'] +
|
||||
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 %}
|
||||
<h2>albums</h2>
|
||||
<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>
|
||||
{% endfor %}
|
||||
</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 . import compat #, signals
|
||||
from .album import Video
|
||||
|
||||
def _has_exif_tags(img):
|
||||
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])
|
||||
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):
|
||||
"""Return the path to the thumb.
|
||||
|
||||
|
@ -195,7 +272,8 @@ def get_thumb(settings, filename):
|
|||
path, filen = os.path.split(filename)
|
||||
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'
|
||||
return os.path.join(path, settings['SIGLICAN_THUMB_DIR'], settings['SIGLICAN_THUMB_PREFIX'] +
|
||||
name + settings['SIGLICAN_THUMB_SUFFIX'] + ext)
|
||||
|
|
31
siglican.py
31
siglican.py
|
@ -20,6 +20,8 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import locale
|
||||
|
@ -92,9 +94,9 @@ class SigalGalleryGenerator(Generator):
|
|||
# this needs to be first to establish pelican settings:
|
||||
super(SigalGalleryGenerator, self).__init__(*args, **kwargs)
|
||||
# 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])
|
||||
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()
|
||||
# this is where we could create a signal if we wanted to, e.g.:
|
||||
# signals.gallery_generator_init.send(self)
|
||||
|
@ -173,7 +175,6 @@ class SigalGalleryGenerator(Generator):
|
|||
path = os.path.join(relpath, d) if relpath != '.' else d
|
||||
if path not in self.albums.keys():
|
||||
dirs.remove(d)
|
||||
|
||||
album = Album(relpath, self.settings, dirs, files, self)
|
||||
|
||||
if not album.medias and not album.albums:
|
||||
|
@ -187,8 +188,14 @@ class SigalGalleryGenerator(Generator):
|
|||
#self._update_context(('albums', )) # unnecessary? **
|
||||
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:
|
||||
for k,v in _DEFAULT_SIGLICAN_SETTINGS.iteritems():
|
||||
for k,v in _DEFAULT_SIGLICAN_SETTINGS.items():
|
||||
if not k in self.context:
|
||||
self.context[k] = v
|
||||
|
||||
|
@ -210,17 +217,23 @@ class SigalGalleryGenerator(Generator):
|
|||
# github8 ** re-integrate multiprocessing logic from Sigal
|
||||
|
||||
# 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
|
||||
for a in albums:
|
||||
logger.debug("siglican: creating directory for %s",a)
|
||||
logger.info("siglican: processing album: %s",a)
|
||||
albums[a].create_output_directories()
|
||||
for media in albums[a].medias:
|
||||
logger.debug("siglican: processing %r , source: %s, dst: %s",
|
||||
media,media.src_path,media.dst_path)
|
||||
if logger.getEffectiveLevel() > logging.INFO:
|
||||
print('.', end='')
|
||||
sys.stdout.flush()
|
||||
if os.path.isfile(media.dst_path):
|
||||
logger.info("siglican: %s exists - skipping", media.filename)
|
||||
self.stats[media.type + '_skipped'] += 1
|
||||
else:
|
||||
logger.info("siglican: processing %r , source: %s, dst: %s",
|
||||
media,media.src_path,media.dst_path)
|
||||
self.stats[media.type] += 1
|
||||
logger.debug("MEDIA TYPE: %s",media.type)
|
||||
# create/move resized images and thumbnails to output dirs:
|
||||
|
@ -230,6 +243,8 @@ class SigalGalleryGenerator(Generator):
|
|||
elif media.type == 'video':
|
||||
process_video(media.src_path,os.path.dirname(
|
||||
media.dst_path),self.settings)
|
||||
if logger.getEffectiveLevel() > logging.INFO:
|
||||
print('')
|
||||
|
||||
# generate the index.html files for the albums
|
||||
if self.settings['SIGLICAN_WRITE_HTML']: # defaults to True
|
||||
|
@ -256,8 +271,10 @@ class SigalGalleryGenerator(Generator):
|
|||
## - bring back Writer options that Sigal had?
|
||||
## - make sure thumbnails don't break in some cases [fixed?]
|
||||
|
||||
|
||||
def get_generators(generators):
|
||||
return SigalGalleryGenerator
|
||||
|
||||
|
||||
def register():
|
||||
signals.get_generators.connect(get_generators)
|
4
video.py
4
video.py
|
@ -30,7 +30,6 @@ import shutil
|
|||
from os.path import splitext
|
||||
|
||||
from . import image
|
||||
from .album import Video
|
||||
from .utils import call_subprocess
|
||||
|
||||
# TODO: merge with image.py
|
||||
|
@ -170,7 +169,8 @@ def get_thumb(settings, filename):
|
|||
path, filen = os.path.split(filename)
|
||||
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'
|
||||
return os.path.join(path, settings['thumb_dir'], settings['thumb_prefix'] +
|
||||
name + settings['thumb_suffix'] + ext)
|
||||
|
|
Loading…
Reference in a new issue