Compare commits

..

No commits in common. "main" and "v0.0.2b" have entirely different histories.

9 changed files with 136 additions and 178 deletions

15
LICENSE
View file

@ -1,5 +1,3 @@
SOFTWARE
The MIT License (MIT)
Copyright (c) 2014 - Scott Boone
@ -20,16 +18,3 @@ 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.

View file

@ -7,8 +7,7 @@ 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 (see "Pelican Configuration Settings" below for
a complete list).
pelicanconf.py as desired.
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.
@ -22,9 +21,7 @@ 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. See *examples/images* for simple examples or the
[Sigal documentation](http://sigal.readthedocs.org/en/latest/) for more
details.
images and metadata.
###Example directory excerpt:
```

View file

@ -33,11 +33,9 @@ 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.
@ -84,7 +82,22 @@ class Media(UnicodeMixin):
@property
def thumbnail(self):
"""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)
def _get_metadata(self):
@ -397,3 +410,81 @@ 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)

View file

@ -4,7 +4,7 @@
{% block content %}
<h2>albums</h2>
<ul>
{% for aname,album in ALBUMS.items() %}
{% for aname,album in ALBUMS.iteritems() %}
<li>{{ aname }}<a href="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.url }}"><img src="{{ SITEURL }}/{{ SIGLICAN_DESTINATION }}/{{ album.thumbnail }}"></a>
{% endfor %}
</ul>

View file

@ -1,3 +0,0 @@
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

@ -1,17 +0,0 @@
{% 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,6 +48,7 @@ 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
@ -176,84 +177,6 @@ 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.
@ -272,8 +195,7 @@ def get_thumb(settings, filename):
path, filen = os.path.split(filename)
name, ext = os.path.splitext(filen)
# TODO: replace this list with Video.extensions github #16
if ext.lower() in ('.mov', '.avi', '.mp4', '.webm', '.ogv'):
if ext.lower() in Video.extensions:
ext = '.jpg'
return os.path.join(path, settings['SIGLICAN_THUMB_DIR'], settings['SIGLICAN_THUMB_PREFIX'] +
name + settings['SIGLICAN_THUMB_SUFFIX'] + ext)

View file

@ -20,8 +20,6 @@
# 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
@ -94,9 +92,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 list(_DEFAULT_SIGLICAN_SETTINGS.keys()):
for k in _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)
@ -175,6 +173,7 @@ 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:
@ -188,14 +187,8 @@ 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.items():
for k,v in _DEFAULT_SIGLICAN_SETTINGS.iteritems():
if not k in self.context:
self.context[k] = v
@ -217,23 +210,17 @@ 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.info("siglican: processing album: %s",a)
logger.debug("siglican: creating directory for %s",a)
albums[a].create_output_directories()
for media in albums[a].medias:
if logger.getEffectiveLevel() > logging.INFO:
print('.', end='')
sys.stdout.flush()
logger.debug("siglican: processing %r , source: %s, dst: %s",
media,media.src_path,media.dst_path)
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:
@ -243,8 +230,6 @@ 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
@ -271,10 +256,8 @@ 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)

View file

@ -30,6 +30,7 @@ import shutil
from os.path import splitext
from . import image
from .album import Video
from .utils import call_subprocess
# TODO: merge with image.py
@ -169,8 +170,7 @@ def get_thumb(settings, filename):
path, filen = os.path.split(filename)
name, ext = os.path.splitext(filen)
# TODO: replace this list with Video.extensions github #16
if ext.lower() in ('.mov', '.avi', '.mp4', '.webm', '.ogv'):
if ext.lower() in Video.extensions:
ext = '.jpg'
return os.path.join(path, settings['thumb_dir'], settings['thumb_prefix'] +
name + settings['thumb_suffix'] + ext)