initial commit - siglican 0.0.1a
This commit is contained in:
parent
bee40aebf9
commit
e5adbe28df
37 changed files with 3500 additions and 1 deletions
20
LICENSE
Normal file
20
LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
Copyright (c) 2014 - Scott Boone
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
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.
|
23
README.md
23
README.md
|
@ -1,4 +1,25 @@
|
|||
siglican
|
||||
========
|
||||
|
||||
a port of the Sigal gallery generator as a Pelican plugin
|
||||
A static gallery generator plugin for Pelican, based on the Sigal
|
||||
Colorbox/Galleria generator by Simon Conseil.
|
||||
|
||||
##Notes
|
||||
|
||||
* The bulk of the code is ported from [Sigal v0.8.0](http://sigal.saimon.org/).
|
||||
* Removal of Sigal process handling, rewriting Sigal settings variables, and
|
||||
integration as a Pelican Generator plugin by Scott Boone.
|
||||
* The core python code used to generate gallery directories and images as well
|
||||
as to populate the Jinja environment with album metadata is in beta. Jinja
|
||||
templates are incomplete.
|
||||
|
||||
## To Do
|
||||
1. Determine the best approach for merging Pelican and Sigal web themes. This
|
||||
will probably require putting Sigal theme information into the Pelican theme
|
||||
in order to facilitate loading Javascript and CSS in the html headers.
|
||||
2. Revise Sigal colorbox and galleria themes for easy inclusion into Pelican
|
||||
themes.
|
||||
3. Change settings names to something other than SIGAL_*
|
||||
4. Unit tests.
|
||||
5. Logging cleanup.
|
||||
6. General code and documentation cleanup.
|
1
__init__.py
Normal file
1
__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .siglican import *
|
490
album.py
Normal file
490
album.py
Normal file
|
@ -0,0 +1,490 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# Album classes for use with siglican plugin along with some helper
|
||||
# methods for context building. This code is largely a copy of gallery.py
|
||||
# from Sigal.
|
||||
|
||||
# Copyright (c) 2009-2014 - Simon Conseil
|
||||
# Copyright (c) 2013 - Christophe-Marie Duquesne
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
# siglican:
|
||||
# Copyright (c) 2014 - Scott Boone (https://github.com/sawall/)
|
||||
# Minor updates from Sigal.
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from .compat import strxfrm, UnicodeMixin, url_quote
|
||||
from .utils import read_markdown, url_from_path
|
||||
|
||||
# TODO ** move logger out to here
|
||||
|
||||
class Media(UnicodeMixin):
|
||||
"""Base Class for media files.
|
||||
|
||||
Attributes:
|
||||
|
||||
- ``type``: ``"image"`` or ``"video"``.
|
||||
- ``filename``: Filename of the resized image.
|
||||
- ``thumbnail``: Location of the corresponding thumbnail image.
|
||||
- ``big``: If not None, location of the unmodified image.
|
||||
- ``exif``: If not None contains a dict with the most common tags. For more
|
||||
information, see :ref:`simple-exif-data`.
|
||||
- ``raw_exif``: If not ``None``, it contains the raw EXIF tags.
|
||||
|
||||
"""
|
||||
|
||||
type = ''
|
||||
extensions = ()
|
||||
|
||||
def __init__(self, filename, path, settings):
|
||||
self.src_filename = self.filename = self.url = filename
|
||||
self.path = path
|
||||
self.settings = settings
|
||||
|
||||
self.src_path = os.path.join(settings['SIGAL_SOURCE'], path, filename)
|
||||
self.dst_path = os.path.join(settings['SIGAL_DESTINATION'], path, filename)
|
||||
|
||||
self.thumb_name = get_thumb(self.settings, self.filename)
|
||||
self.thumb_path = os.path.join(settings['SIGAL_DESTINATION'], path, self.thumb_name)
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.raw_exif = None
|
||||
self.exif = None
|
||||
self.date = None
|
||||
self._get_metadata()
|
||||
#signals.media_initialized.send(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s>(%r)" % (self.__class__.__name__, str(self))
|
||||
|
||||
def __unicode__(self):
|
||||
return os.path.join(self.path, self.filename)
|
||||
|
||||
@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['SIGAL_THUMB_SIZE'],
|
||||
fit=self.settings['SIGAL_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):
|
||||
""" Get image metadata from filename.md: title, description, meta."""
|
||||
self.description = ''
|
||||
self.meta = {}
|
||||
self.title = ''
|
||||
|
||||
descfile = os.path.splitext(self.src_path)[0] + '.md'
|
||||
if os.path.isfile(descfile):
|
||||
meta = read_markdown(descfile)
|
||||
for key, val in meta.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
|
||||
class Image(Media):
|
||||
"""Gather all informations on an image file."""
|
||||
|
||||
type = 'image'
|
||||
extensions = ('.jpg', '.jpeg', '.png')
|
||||
|
||||
def __init__(self, filename, path, settings):
|
||||
super(Image, self).__init__(filename, path, settings)
|
||||
self.raw_exif, self.exif = get_exif_tags(self.src_path)
|
||||
if self.exif is not None and 'dateobj' in self.exif:
|
||||
self.date = self.exif['dateobj']
|
||||
|
||||
|
||||
class Video(Media):
|
||||
"""Gather all informations on a video file."""
|
||||
|
||||
type = 'video'
|
||||
extensions = ('.mov', '.avi', '.mp4', '.webm', '.ogv')
|
||||
|
||||
def __init__(self, filename, path, settings):
|
||||
super(Video, self).__init__(filename, path, settings)
|
||||
base = os.path.splitext(filename)[0]
|
||||
self.src_filename = filename
|
||||
self.filename = self.url = base + '.webm'
|
||||
self.dst_path = os.path.join(settings['SIGAL_DESTINATION'], path, base + '.webm')
|
||||
|
||||
|
||||
# minimally modified from Sigal's gallery.Album class
|
||||
class Album(object):
|
||||
description_file = "index.md"
|
||||
output_file = 'index.html'
|
||||
|
||||
def __init__(self, path, settings, dirnames, filenames, gallery):
|
||||
self.path = path
|
||||
self.name = path.split(os.path.sep)[-1]
|
||||
self.gallery = gallery
|
||||
self.settings = settings
|
||||
self.orig_path = None
|
||||
self._thumbnail = None
|
||||
|
||||
# set up source and destination paths
|
||||
if path == '.':
|
||||
self.src_path = settings['SIGAL_SOURCE']
|
||||
self.dst_path = settings['SIGAL_DESTINATION']
|
||||
else:
|
||||
self.src_path = os.path.join(settings['SIGAL_SOURCE'], path)
|
||||
self.dst_path = os.path.join(settings['SIGAL_DESTINATION'], path)
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self._get_metadata() # this reads the index.md file
|
||||
|
||||
# optionally add index.html to the URLs
|
||||
# ** don't understand purpose of this; default is False
|
||||
self.url_ext = self.output_file if settings['SIGAL_INDEX_IN_URL'] else ''
|
||||
|
||||
# creates appropriate subdirectory for the album
|
||||
self.index_url = url_from_path(os.path.relpath(
|
||||
settings['SIGAL_DESTINATION'], self.dst_path)) + '/' + self.url_ext
|
||||
|
||||
# sort sub-albums
|
||||
dirnames.sort(key=strxfrm, reverse=settings['SIGAL_ALBUMS_SORT_REVERSE'])
|
||||
self.subdirs = dirnames
|
||||
|
||||
#: List of all medias in the album (:class:`~sigal.gallery.Image` and
|
||||
#: :class:`~sigal.gallery.Video`).
|
||||
self.medias = medias = []
|
||||
self.medias_count = defaultdict(int)
|
||||
|
||||
# create Media objects
|
||||
for f in filenames:
|
||||
ext = os.path.splitext(f)[1]
|
||||
if ext.lower() in Image.extensions:
|
||||
media = Image(f, self.path, settings)
|
||||
elif ext.lower() in Video.extensions:
|
||||
media = Video(f, self.path, settings)
|
||||
else:
|
||||
continue
|
||||
|
||||
self.medias_count[media.type] += 1
|
||||
medias.append(media)
|
||||
|
||||
# sort images
|
||||
if medias:
|
||||
medias_sort_attr = settings['SIGAL_MEDIAS_SORT_ATTR']
|
||||
if medias_sort_attr == 'date':
|
||||
key = lambda s: s.date or datetime.now()
|
||||
else:
|
||||
key = lambda s: strxfrm(getattr(s, medias_sort_attr))
|
||||
|
||||
medias.sort(key=key, reverse=settings['SIGAL_MEDIAS_SORT_REVERSE'])
|
||||
|
||||
#signals.album_initialized.send(self)
|
||||
|
||||
# string representation of Album for debug statements
|
||||
def __repr__(self):
|
||||
return "<%s>(path=%r, title=%r, assets:%r)" % (self.__class__.__name__,
|
||||
self.path, self.title, self.medias)
|
||||
|
||||
def __unicode__(self):
|
||||
return (u"{} : ".format(self.path) +
|
||||
', '.join("{} {}s".format(count, _type)
|
||||
for _type, count in self.medias_count.items()))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.medias)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.medias)
|
||||
|
||||
def _get_metadata(self):
|
||||
"""Get album metadata from `description_file` (`index.md`):
|
||||
|
||||
-> title, thumbnail image, description
|
||||
|
||||
"""
|
||||
descfile = os.path.join(self.src_path, self.description_file)
|
||||
self.description = ''
|
||||
self.meta = {}
|
||||
# default: get title from directory name
|
||||
self.title = os.path.basename(self.path if self.path != '.'
|
||||
else self.src_path)
|
||||
|
||||
if os.path.isfile(descfile):
|
||||
meta = read_markdown(descfile)
|
||||
for key, val in meta.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
def create_output_directories(self):
|
||||
"""Create output directories for thumbnails and original images."""
|
||||
|
||||
def check_or_create_dir(path):
|
||||
if not os.path.isdir(path):
|
||||
os.makedirs(path)
|
||||
|
||||
check_or_create_dir(self.dst_path)
|
||||
|
||||
if self.medias:
|
||||
check_or_create_dir(os.path.join(self.dst_path,
|
||||
self.settings['SIGAL_THUMB_DIR']))
|
||||
|
||||
#if self.medias and self.settings['SIGAL_KEEP_ORIG']:
|
||||
# self.orig_path = os.path.join(self.dst_path, self.settings['SIGAL_ORIG_DIR'])
|
||||
# check_or_create_dir(self.orig_path)
|
||||
|
||||
@property
|
||||
def images(self):
|
||||
"""List of images (:class:`~sigal.gallery.Image`)."""
|
||||
for media in self.medias:
|
||||
if media.type == 'image':
|
||||
yield media
|
||||
|
||||
@property
|
||||
def videos(self):
|
||||
"""List of videos (:class:`~sigal.gallery.Video`)."""
|
||||
for media in self.medias:
|
||||
if media.type == 'video':
|
||||
yield media
|
||||
|
||||
@property
|
||||
def albums(self):
|
||||
"""List of :class:`~sigal.gallery.Album` objects for each
|
||||
sub-directory.
|
||||
"""
|
||||
root_path = self.path if self.path != '.' else ''
|
||||
return [self.gallery.albums[os.path.join(root_path, path)]
|
||||
for path in self.subdirs]
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""URL of the album, relative to its parent."""
|
||||
url = self.name.encode('utf-8')
|
||||
return url_quote(url) + '/' + self.url_ext
|
||||
|
||||
@property
|
||||
def thumbnail(self):
|
||||
"""Path to the thumbnail of the album."""
|
||||
|
||||
if self._thumbnail:
|
||||
# stop if it is already set
|
||||
return url_from_path(self._thumbnail)
|
||||
|
||||
# Test the thumbnail from the Markdown file.
|
||||
thumbnail = self.meta.get('thumbnail', [''])[0]
|
||||
|
||||
if thumbnail and os.path.isfile(os.path.join(self.src_path, thumbnail)):
|
||||
self._thumbnail = os.path.join(self.name, get_thumb(self.settings,
|
||||
thumbnail))
|
||||
self.logger.debug("Thumbnail for %r : %s", self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
else:
|
||||
# find and return the first landscape image
|
||||
for f in self.medias:
|
||||
ext = os.path.splitext(f.filename)[1]
|
||||
if ext.lower() in Image.extensions:
|
||||
im = PILImage.open(f.src_path)
|
||||
if im.size[0] > im.size[1]:
|
||||
self._thumbnail = os.path.join(self.name, f.thumbnail)
|
||||
self.logger.debug(
|
||||
"Use 1st landscape image as thumbnail for %r : %s",
|
||||
self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
|
||||
# else simply return the 1st media file
|
||||
if not self._thumbnail and self.medias:
|
||||
self._thumbnail = os.path.join(self.name, self.medias[0].thumbnail)
|
||||
self.logger.debug("Use the 1st image as thumbnail for %r : %s",
|
||||
self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
|
||||
# use the thumbnail of their sub-directories
|
||||
if not self._thumbnail:
|
||||
for path, album in self.gallery.get_albums(self.path):
|
||||
if album.thumbnail:
|
||||
self._thumbnail = os.path.join(self.name, album.thumbnail)
|
||||
self.logger.debug(
|
||||
"Using thumbnail from sub-directory for %r : %s",
|
||||
self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
|
||||
self.logger.error('Thumbnail not found for %r', self)
|
||||
return None
|
||||
|
||||
@property
|
||||
def breadcrumb(self):
|
||||
"""List of ``(url, title)`` tuples defining the current breadcrumb
|
||||
path.
|
||||
"""
|
||||
if self.path == '.':
|
||||
return []
|
||||
|
||||
path = self.path
|
||||
breadcrumb = [((self.url_ext or '.'), self.title)]
|
||||
|
||||
while True:
|
||||
path = os.path.normpath(os.path.join(path, '..'))
|
||||
if path == '.':
|
||||
break
|
||||
|
||||
url = (url_from_path(os.path.relpath(path, self.path)) + '/' +
|
||||
self.url_ext)
|
||||
breadcrumb.append((url, self.gallery.albums[path].title))
|
||||
|
||||
breadcrumb.reverse()
|
||||
return breadcrumb
|
||||
|
||||
@property
|
||||
def zip(self):
|
||||
"""Make a ZIP archive with all media files and return its path.
|
||||
|
||||
If the ``zip_gallery`` setting is set,it contains the location of a zip
|
||||
archive with all original images of the corresponding directory.
|
||||
|
||||
"""
|
||||
zip_gallery = self.settings['SIGAL_ZIP_GALLERY']
|
||||
|
||||
if zip_gallery and len(self) > 0:
|
||||
archive_path = os.path.join(self.dst_path, zip_gallery)
|
||||
archive = zipfile.ZipFile(archive_path, 'w')
|
||||
|
||||
if self.settings['SIGAL_ZIP_MEDIA_FORMAT'] == 'orig':
|
||||
for p in self:
|
||||
archive.write(p.src_path, os.path.split(p.src_path)[1])
|
||||
else:
|
||||
for p in self:
|
||||
archive.write(p.dst_path, os.path.split(p.dst_path)[1])
|
||||
|
||||
archive.close()
|
||||
self.logger.debug('Created ZIP archive %s', archive_path)
|
||||
return zip_gallery
|
||||
else:
|
||||
return None
|
||||
|
||||
# ** TODO: move as part of utils cleanup
|
||||
def get_thumb(settings, filename):
|
||||
"""Return the path to the thumb.
|
||||
|
||||
examples:
|
||||
>>> default_settings = create_settings()
|
||||
>>> get_thumb(default_settings, "bar/foo.jpg")
|
||||
"bar/thumbnails/foo.jpg"
|
||||
>>> get_thumb(default_settings, "bar/foo.png")
|
||||
"bar/thumbnails/foo.png"
|
||||
|
||||
for videos, it returns a jpg file:
|
||||
>>> get_thumb(default_settings, "bar/foo.webm")
|
||||
"bar/thumbnails/foo.jpg"
|
||||
"""
|
||||
|
||||
path, filen = os.path.split(filename)
|
||||
name, ext = os.path.splitext(filen)
|
||||
|
||||
if ext.lower() in Video.extensions:
|
||||
ext = '.jpg'
|
||||
return os.path.join(path, settings['SIGAL_THUMB_DIR'], settings['SIGAL_THUMB_PREFIX'] +
|
||||
name + settings['SIGAL_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)
|
54
compat.py
Normal file
54
compat.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2013-2014 - Simon Conseil
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
## ** TODO: use pelican's compatibility features in utils.py (which uses
|
||||
## Django calls)
|
||||
|
||||
import locale
|
||||
import sys
|
||||
|
||||
# the following appears to be from Django and/or Jinja
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
if not PY2:
|
||||
text_type = str
|
||||
string_types = (str,)
|
||||
unichr = chr
|
||||
strxfrm = locale.strxfrm
|
||||
from urllib.parse import quote as url_quote
|
||||
else:
|
||||
text_type = unicode # NOQA
|
||||
string_types = (str, unicode) # NOQA
|
||||
unichr = unichr
|
||||
|
||||
def strxfrm(s):
|
||||
return locale.strxfrm(s.encode('utf-8'))
|
||||
|
||||
from urllib import quote as url_quote # NOQA
|
||||
|
||||
# the following appears to be from
|
||||
# http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/
|
||||
class UnicodeMixin(object):
|
||||
if not PY2:
|
||||
__str__ = lambda x: x.__unicode__()
|
||||
else:
|
||||
__str__ = lambda x: unicode(x).encode('utf-8')
|
202
image.py
Normal file
202
image.py
Normal file
|
@ -0,0 +1,202 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# Copyright (c) 2009-2014 - Simon Conseil
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
# Additional copyright notice:
|
||||
#
|
||||
# Several lines of code concerning extraction of GPS data from EXIF tags where
|
||||
# taken from a GitHub Gist by Eran Sandler at
|
||||
#
|
||||
# https://gist.github.com/erans/983821
|
||||
#
|
||||
# and partially modified. The code in question is licensed under MIT license.
|
||||
|
||||
# Copyright (c) 2014 - Scott Boone (https://github.com/sawall/)
|
||||
# Minor updates to image.py from Sigal.
|
||||
|
||||
# TODO: merge with video.py
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pilkit.processors
|
||||
import sys
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from PIL.ExifTags import TAGS, GPSTAGS
|
||||
from PIL import Image as PILImage
|
||||
from PIL import ImageOps
|
||||
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
|
||||
|
||||
|
||||
def generate_image(source, outname, settings, options=None):
|
||||
"""Image processor, rotate and resize the image.
|
||||
|
||||
:param source: path to an image
|
||||
:param outname: output filename
|
||||
:param settings: settings dict
|
||||
:param options: dict with PIL options (quality, optimize, progressive)
|
||||
|
||||
"""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
img = PILImage.open(source)
|
||||
original_format = img.format
|
||||
|
||||
if settings['SIGAL_COPY_EXIF_DATA'] and settings['SIGAL_AUTOROTATE_IMAGES']:
|
||||
logger.warning("The 'autorotate_images' and 'copy_exif_data' settings "
|
||||
"are not compatible because Sigal can't save the "
|
||||
"modified Orientation tag.")
|
||||
|
||||
# Preserve EXIF data
|
||||
if settings['SIGAL_COPY_EXIF_DATA'] and _has_exif_tags(img):
|
||||
if options is not None:
|
||||
options = deepcopy(options)
|
||||
else:
|
||||
options = {}
|
||||
options['exif'] = img.info['exif']
|
||||
|
||||
# Rotate the img, and catch IOError when PIL fails to read EXIF
|
||||
if settings['SIGAL_AUTOROTATE_IMAGES']:
|
||||
try:
|
||||
img = Transpose().process(img)
|
||||
except (IOError, IndexError):
|
||||
pass
|
||||
|
||||
# Resize the image
|
||||
if settings['SIGAL_IMG_PROCESSOR']:
|
||||
try:
|
||||
logger.debug('Processor: %s', settings['SIGAL_IMG_PROCESSOR'])
|
||||
processor_cls = getattr(pilkit.processors,
|
||||
settings['SIGAL_IMG_PROCESSOR'])
|
||||
except AttributeError:
|
||||
logger.error('Wrong processor name: %s', settings['SIGAL_IMG_PROCESSOR'])
|
||||
sys.exit()
|
||||
|
||||
processor = processor_cls(*settings['SIGAL_IMG_SIZE'], upscale=False)
|
||||
img = processor.process(img)
|
||||
|
||||
# TODO ** delete (maintained from Sigal for reference)
|
||||
# signal.send() does not work here as plugins can modify the image, so we
|
||||
# iterate other the receivers to call them with the image.
|
||||
#for receiver in signals.img_resized.receivers_for(img):
|
||||
# img = receiver(img, settings=settings)
|
||||
|
||||
outformat = img.format or original_format or 'JPEG'
|
||||
logger.debug(u'Save resized image to {0} ({1})'.format(outname, outformat))
|
||||
save_image(img, outname, outformat, options=options, autoconvert=True)
|
||||
|
||||
|
||||
def generate_thumbnail(source, outname, box, fit=True, options=None):
|
||||
"""Create a thumbnail image."""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
img = PILImage.open(source)
|
||||
original_format = img.format
|
||||
|
||||
if fit:
|
||||
img = ImageOps.fit(img, box, PILImage.ANTIALIAS)
|
||||
else:
|
||||
img.thumbnail(box, PILImage.ANTIALIAS)
|
||||
|
||||
outformat = img.format or original_format or 'JPEG'
|
||||
logger.debug(u'Save thumnail image: {0} ({1})'.format(outname, outformat))
|
||||
save_image(img, outname, outformat, options=options, autoconvert=True)
|
||||
|
||||
|
||||
def process_image(filepath, outpath, settings):
|
||||
"""Process one image: resize, create thumbnail."""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
filename = os.path.split(filepath)[1]
|
||||
outname = os.path.join(outpath, filename)
|
||||
ext = os.path.splitext(filename)[1]
|
||||
|
||||
if ext in ('.jpg', '.jpeg', '.JPG', '.JPEG'):
|
||||
options = settings['SIGAL_JPG_OPTIONS']
|
||||
elif ext == '.png':
|
||||
options = {'optimize': True}
|
||||
else:
|
||||
options = {}
|
||||
|
||||
try:
|
||||
generate_image(filepath, outname, settings, options=options)
|
||||
except Exception as e:
|
||||
logger.error('Failed to process image: %s', e)
|
||||
return
|
||||
|
||||
if settings['SIGAL_MAKE_THUMBS']:
|
||||
thumb_name = os.path.join(outpath, get_thumb(settings, filename))
|
||||
generate_thumbnail(outname, thumb_name, settings['SIGAL_THUMB_SIZE'],
|
||||
fit=settings['SIGAL_THUMB_FIT'], options=options)
|
||||
|
||||
|
||||
def _get_exif_data(filename):
|
||||
"""Return a dict with EXIF data."""
|
||||
|
||||
img = PILImage.open(filename)
|
||||
exif = img._getexif() or {}
|
||||
data = {TAGS.get(tag, tag): value for tag, value in exif.items()}
|
||||
|
||||
if 'GPSInfo' in data:
|
||||
data['GPSInfo'] = {GPSTAGS.get(tag, tag): value
|
||||
for tag, value in data['GPSInfo'].items()}
|
||||
return data
|
||||
|
||||
|
||||
def dms_to_degrees(v):
|
||||
"""Convert degree/minute/second to decimal degrees."""
|
||||
|
||||
d = float(v[0][0]) / float(v[0][1])
|
||||
m = float(v[1][0]) / float(v[1][1])
|
||||
s = float(v[2][0]) / float(v[2][1])
|
||||
return d + (m / 60.0) + (s / 3600.0)
|
||||
|
||||
def get_thumb(settings, filename):
|
||||
"""Return the path to the thumb.
|
||||
|
||||
examples:
|
||||
>>> default_settings = create_settings()
|
||||
>>> get_thumb(default_settings, "bar/foo.jpg")
|
||||
"bar/thumbnails/foo.jpg"
|
||||
>>> get_thumb(default_settings, "bar/foo.png")
|
||||
"bar/thumbnails/foo.png"
|
||||
|
||||
for videos, it returns a jpg file:
|
||||
>>> get_thumb(default_settings, "bar/foo.webm")
|
||||
"bar/thumbnails/foo.jpg"
|
||||
"""
|
||||
|
||||
path, filen = os.path.split(filename)
|
||||
name, ext = os.path.splitext(filen)
|
||||
|
||||
if ext.lower() in Video.extensions:
|
||||
ext = '.jpg'
|
||||
return os.path.join(path, settings['SIGAL_THUMB_DIR'], settings['SIGAL_THUMB_PREFIX'] +
|
||||
name + settings['SIGAL_THUMB_SUFFIX'] + ext)
|
||||
|
28
pkgmeta.py
Normal file
28
pkgmeta.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 - Scott Boone
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
__title__ = 'siglican'
|
||||
__author__ = 'Scott Boone'
|
||||
__version__ = '0.0.1-alpha'
|
||||
__license__ = 'MIT'
|
||||
__url__ = 'https://github.com/sawall/siglican'
|
||||
__all__ = ['__title__', '__author__', '__version__', '__license__', '__url__']
|
284
siglican.py
Normal file
284
siglican.py
Normal file
|
@ -0,0 +1,284 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 - Scott Boone
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import locale
|
||||
import logging
|
||||
import fnmatch
|
||||
from pelican import signals
|
||||
from pelican.generators import Generator
|
||||
from .compat import PY2
|
||||
from .album import Album
|
||||
from .image import process_image
|
||||
from .video import process_video
|
||||
from .writer import Writer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Default config from Sigal's settings module. These have been changed to
|
||||
# upper case because Pelican does not recognize lower case configuration names.
|
||||
_DEFAULT_SIGAL_SETTINGS = {
|
||||
'SIGAL_ALBUMS_SORT_REVERSE': False,
|
||||
'SIGAL_AUTOROTATE_IMAGES': True,
|
||||
'SIGAL_COLORBOX_COLUMN_SIZE': 4,
|
||||
'SIGAL_COPY_EXIF_DATA': False,
|
||||
'SIGAL_DESTINATION': 'gallery',
|
||||
'SIGAL_FILES_TO_COPY': (),
|
||||
# 'GOOGLE_ANALYTICS': '',
|
||||
'SIGAL_IGNORE_DIRECTORIES': ['.'], # using a pelican theme template as base
|
||||
'SIGAL_IGNORE_FILES': [],
|
||||
'SIGAL_IMG_PROCESSOR': 'ResizeToFit',
|
||||
'SIGAL_IMG_SIZE': (640, 480),
|
||||
'SIGAL_INDEX_IN_URL': False,
|
||||
'SIGAL_JPG_OPTIONS': {'quality': 85, 'optimize': True, 'progressive': True},
|
||||
# 'SIGAL_KEEP_ORIG': False,
|
||||
'SIGAL_LINKS': '',
|
||||
'SIGAL_LOCALE': '',
|
||||
'SIGAL_MEDIAS_SORT_ATTR': 'filename',
|
||||
'SIGAL_MEDIAS_SORT_REVERSE': False,
|
||||
'SIGAL_MAKE_THUMBS': True,
|
||||
'SIGAL_ORIG_DIR': 'original',
|
||||
'SIGAL_ORIG_LINK': False,
|
||||
# 'PLUGINS': [],
|
||||
# 'PLUGIN_PATHS': [],
|
||||
'SIGAL_SOURCE': 'sigal',
|
||||
'SIGAL_THEME': 'colorbox',
|
||||
'SIGAL_THUMB_DIR': 'thumbs',
|
||||
'SIGAL_THUMB_FIT': True,
|
||||
'SIGAL_THUMB_PREFIX': '',
|
||||
'SIGAL_THUMB_SIZE': (200, 150),
|
||||
'SIGAL_THUMB_SUFFIX': '',
|
||||
'SIGAL_VIDEO_SIZE': (480, 360),
|
||||
'SIGAL_WEBM_OPTIONS': ['-crf', '10', '-b:v', '1.6M',
|
||||
'-qmin', '4', '-qmax', '63'],
|
||||
'SIGAL_WRITE_HTML': True,
|
||||
'SIGAL_ZIP_GALLERY': False,
|
||||
'SIGAL_ZIP_MEDIA_FORMAT': 'resized',
|
||||
}
|
||||
|
||||
# Generator class used to generate plugin context and write.
|
||||
# TODO: consider usinge CachingGenerator instead?
|
||||
class SigalGalleryGenerator(Generator):
|
||||
# reference: methods provided by Pelican Generator:
|
||||
# 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_files(self, paths, exclude=[], extensions=None): paths to search,
|
||||
# exclude, allowed extensions
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize gallery dict and load in custom Sigal settings."""
|
||||
|
||||
logger.debug("siglican: entering SigalGalleryGenerator.__init__")
|
||||
self.albums = {}
|
||||
# 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_SIGAL_SETTINGS.keys()[:]:
|
||||
self.settings[k] = self.settings.get(k, _DEFAULT_SIGAL_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)
|
||||
|
||||
def _clean_settings(self):
|
||||
"""Checks existence of directories and normalizes image size settings."""
|
||||
|
||||
# create absolute paths to source, theme and destination directories:
|
||||
init_source = self.settings['SIGAL_SOURCE']
|
||||
self.settings['SIGAL_SOURCE'] = os.path.normpath(self.settings['PATH'] +
|
||||
"/../" + self.settings['SIGAL_SOURCE'] + '/images')
|
||||
self.settings['SIGAL_THEME'] = os.path.normpath(self.settings['PATH'] +
|
||||
"/../" + init_source + "/" + self.settings['SIGAL_THEME'])
|
||||
self.settings['SIGAL_DESTINATION'] = os.path.normpath(
|
||||
self.settings['OUTPUT_PATH'] + "/" + self.settings['SIGAL_DESTINATION'])
|
||||
|
||||
enc = locale.getpreferredencoding() if PY2 else None
|
||||
|
||||
# test for existence of source directories
|
||||
pathkeys = ['SIGAL_SOURCE', 'SIGAL_THEME']
|
||||
for k in pathkeys:
|
||||
if os.path.isdir(self.settings[k]):
|
||||
# convert to unicode for os.walk dirname/filename
|
||||
if PY2 and isinstance(self.settings[k], str):
|
||||
self.settings[k] = self.settings[k].decode(enc)
|
||||
logger.debug("siglican: %s = %s",k,self.settings[k])
|
||||
else:
|
||||
logger.error("siglican: missing source directory %s: %s",
|
||||
k,self.settings[k])
|
||||
sys.exit(1)
|
||||
|
||||
# normalize sizes as e landscape
|
||||
for key in ('SIGAL_IMG_SIZE', 'SIGAL_THUMB_SIZE', 'SIGAL_VIDEO_SIZE'):
|
||||
w, h = self.settings[key]
|
||||
if h > w:
|
||||
self.settings[key] = (h, w)
|
||||
logger.warning("siglican: The %s setting should be specified "
|
||||
"with the largest value first.", key)
|
||||
|
||||
if not self.settings['SIGAL_IMG_PROCESSOR']:
|
||||
logger.info('No Processor, images will not be resized')
|
||||
|
||||
# based on Sigal's Gallery.__init__() method:
|
||||
def generate_context(self):
|
||||
""""Update the global Pelican context that's shared between generators."""
|
||||
|
||||
logger.debug("siglican: in generate_context()")
|
||||
locale.setlocale(locale.LC_ALL, self.settings['SIGAL_LOCALE'])
|
||||
self.stats = {'image': 0, 'image_skipped': 0,
|
||||
'video': 0, 'video_skipped': 0}
|
||||
# build the list of directories with images
|
||||
# ** TODO: add error checking, consider use of get(), etc.
|
||||
src_path = self.settings['SIGAL_SOURCE']
|
||||
ignore_dirs = self.settings['SIGAL_IGNORE_DIRECTORIES']
|
||||
ignore_files = self.settings['SIGAL_IGNORE_FILES']
|
||||
for path, dirs, files in os.walk(src_path, followlinks=True,
|
||||
topdown=False):
|
||||
relpath = os.path.relpath(path, src_path)
|
||||
if ignore_dirs and any(fnmatch.fnmatch(relpath, ignore)
|
||||
for ignore in ignore_dirs):
|
||||
logger.info('siglican: ignoring %s', relpath)
|
||||
continue
|
||||
if ignore_files: # << ** BUG: if no ignore_files, then no files?
|
||||
files_path = {os.path.join(relpath, f) for f in files}
|
||||
for ignore in ignore_files:
|
||||
files_path -= set(fnmatch.filter(files_path, ignore))
|
||||
## ** BUG? unicode in list may cause mismatch
|
||||
logger.debug('siglican: Files before filtering: %r', files)
|
||||
files = [os.path.split(f)[1] for f in files_path]
|
||||
logger.debug('siglican: Files after filtering: %r', files)
|
||||
|
||||
# Remove sub-directories that have been ignored in a previous
|
||||
# iteration (as topdown=False, sub-directories are processed before
|
||||
# their parent
|
||||
for d in dirs[:]:
|
||||
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:
|
||||
logger.info('siglican: Skip empty album: %r', album)
|
||||
else:
|
||||
self.albums[relpath] = album
|
||||
# done generating context (self.albums) now
|
||||
logger.debug('siglican: albums:\n%r', self.albums.values())
|
||||
# BUG ** : albums appears to contain one extra empty entry at this point
|
||||
# <Album>(path='.', title=u'images', assets:[])]
|
||||
|
||||
# update the jinja context so that templates can access it:
|
||||
self._update_context(('albums', )) # ** not 100% certain this is needed
|
||||
self.context['ALBUMS'] = self.albums
|
||||
|
||||
# update the jinja context with the default sigal settings:
|
||||
for k,v in _DEFAULT_SIGAL_SETTINGS.iteritems():
|
||||
if not k in self.context:
|
||||
self.context[k] = v
|
||||
|
||||
def generate_output(self, writer):
|
||||
""" Creates gallery destination directories, thumbnails, resized
|
||||
images, and moves everything into the destination."""
|
||||
|
||||
# ignore the writer because it might be from another plugin
|
||||
# see https://github.com/getpelican/pelican/issues/1459
|
||||
|
||||
# DELETE ** I don't think I need to do anything like this:
|
||||
# create a gallery in pages so that we can reference it from pelican
|
||||
#for page in self.pages:
|
||||
# if page.metadata.get('template') == 'sigal_gallery':
|
||||
# page.gallery=gallery
|
||||
|
||||
# create destination directory
|
||||
if not os.path.isdir(self.settings['SIGAL_DESTINATION']):
|
||||
os.makedirs(self.settings['SIGAL_DESTINATION'])
|
||||
|
||||
# TODO ** add lots of error/exception catching
|
||||
# TODO ** re-integrate multiprocessing logic from Sigal
|
||||
|
||||
# generate thumbnails, process images, and move them to the destination
|
||||
albums = self.albums
|
||||
for a in albums:
|
||||
logger.debug("siglican: creating directory for %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 os.path.isfile(media.dst_path):
|
||||
logger.info("siglican: %s exists - skipping", media.filename)
|
||||
self.stats[media.type + '_skipped'] += 1
|
||||
else:
|
||||
self.stats[media.type] += 1
|
||||
logger.debug("MEDIA TYPE: %s",media.type)
|
||||
# create/move resized images and thumbnails to output dirs:
|
||||
if media.type == 'image':
|
||||
process_image(media.src_path,os.path.dirname(
|
||||
media.dst_path),self.settings)
|
||||
elif media.type == 'video':
|
||||
process_video(media.src_path,os.path.dirname(
|
||||
media.dst_path),self.settings)
|
||||
|
||||
# generate the index.html files for the albums
|
||||
if self.settings['SIGAL_WRITE_HTML']: # defaults to True
|
||||
# locate the theme; check for a custom theme in ./sigal/themes, if not
|
||||
# found, look for a default in siglican/themes
|
||||
self.theme = self.settings['SIGAL_THEME']
|
||||
default_themes = os.path.normpath(os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)), 'themes'))
|
||||
logger.debug("siglican: custom theme: %s", self.theme)
|
||||
logger.debug("siglican: default themedir: %s", default_themes)
|
||||
if not os.path.exists(self.theme):
|
||||
self.theme = os.path.join(default_themes, os.path.basename(self.theme))
|
||||
if not os.path.exists(self.theme):
|
||||
raise Exception("siglican: unable to find theme: %s" %
|
||||
os.path.basename(self.theme))
|
||||
|
||||
logger.info("siglican theme: %s", self.theme)
|
||||
theme_relpath = os.path.join(self.theme, 'templates')
|
||||
|
||||
## note 1: it's impossible to add additional templates to jinja
|
||||
## after the initial init, which means we either need to put plugin
|
||||
## templates in with the rest of the pelican templates or use a
|
||||
## plugin-specific jinja environment and writer
|
||||
|
||||
# note 2: when Pelican calls generate_output() on a Generator plugin,
|
||||
# it's uncertain which Writer will be sent; if other plugins with
|
||||
# Writers are loaded, it might be one of those Writers instead of
|
||||
# one of the core Pelican writers. thus this plugin explicitly calls
|
||||
# a Writer so that it doesn't get any nasty surprises due to plugin
|
||||
# conflicts. I logged a feature request to Pelican here:
|
||||
# https://github.com/getpelican/pelican/issues/1459
|
||||
|
||||
self.writer = Writer(self.context, self.theme, 'album')
|
||||
for album in self.albums.values():
|
||||
self.writer.write(album)
|
||||
|
||||
## possible cleanup:
|
||||
## - missing some writer options that Sigal had, bring back?
|
||||
## - make sure all necessary template info is accessible by the writer
|
||||
## - make sure thumbnails don't break in some cases
|
||||
|
||||
def get_generators(generators):
|
||||
return SigalGalleryGenerator
|
||||
|
||||
def register():
|
||||
signals.get_generators.connect(get_generators)
|
16
themes/Makefile
Normal file
16
themes/Makefile
Normal file
|
@ -0,0 +1,16 @@
|
|||
COLORBOX_PATH=colorbox/static/css
|
||||
GALLERIA_PATH=galleria/static/css
|
||||
|
||||
all:
|
||||
sass $(COLORBOX_PATH)/style.scss:$(COLORBOX_PATH)/style.min.css --style compressed
|
||||
sass $(GALLERIA_PATH)/style.scss:$(GALLERIA_PATH)/style.min.css --style compressed
|
||||
|
||||
colorbox:
|
||||
sass --watch $(COLORBOX_PATH)/style.scss:$(COLORBOX_PATH)/style.min.css \
|
||||
--style compressed
|
||||
|
||||
galleria:
|
||||
sass --watch $(GALLERIA_PATH)/style.scss:$(GALLERIA_PATH)/style.min.css \
|
||||
--style compressed
|
||||
|
||||
.PHONY: colorbox galleria
|
269
themes/colorbox/static/css/base.scss
Normal file
269
themes/colorbox/static/css/base.scss
Normal file
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* Skeleton V1.2
|
||||
* Copyright 2011, Dave Gamache
|
||||
* www.getskeleton.com
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* 6/20/2012
|
||||
*/
|
||||
|
||||
|
||||
/* Table of Content
|
||||
==================================================
|
||||
#Reset & Basics
|
||||
#Basic Styles
|
||||
#Site Styles
|
||||
#Typography
|
||||
#Links
|
||||
#Lists
|
||||
#Images
|
||||
#Buttons
|
||||
#Forms
|
||||
#Misc */
|
||||
|
||||
|
||||
/* #Reset & Basics (Inspired by E. Meyers)
|
||||
================================================== */
|
||||
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline; }
|
||||
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
|
||||
display: block; }
|
||||
body {
|
||||
line-height: 1; }
|
||||
ol, ul {
|
||||
list-style: none; }
|
||||
blockquote, q {
|
||||
quotes: none; }
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none; }
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0; }
|
||||
|
||||
|
||||
/* #Basic Styles
|
||||
================================================== */
|
||||
body {
|
||||
background: #fff;
|
||||
font: 14px/21px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #444;
|
||||
-webkit-font-smoothing: antialiased; /* Fix for webkit rendering */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
|
||||
/* #Typography
|
||||
================================================== */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #181818;
|
||||
font-family: "Georgia", "Times New Roman", serif;
|
||||
font-weight: normal; }
|
||||
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; }
|
||||
h1 { font-size: 46px; line-height: 50px; margin-bottom: 14px;}
|
||||
h2 { font-size: 35px; line-height: 40px; margin-bottom: 10px; }
|
||||
h3 { font-size: 28px; line-height: 34px; margin-bottom: 8px; }
|
||||
h4 { font-size: 21px; line-height: 30px; margin-bottom: 4px; }
|
||||
h5 { font-size: 17px; line-height: 24px; }
|
||||
h6 { font-size: 14px; line-height: 21px; }
|
||||
.subheader { color: #777; }
|
||||
|
||||
p { margin: 0 0 20px 0; }
|
||||
p img { margin: 0; }
|
||||
p.lead { font-size: 21px; line-height: 27px; color: #777; }
|
||||
|
||||
em { font-style: italic; }
|
||||
strong { font-weight: bold; color: #333; }
|
||||
small { font-size: 80%; }
|
||||
|
||||
/* Blockquotes */
|
||||
blockquote, blockquote p { font-size: 17px; line-height: 24px; color: #777; font-style: italic; }
|
||||
blockquote { margin: 0 0 20px; padding: 9px 20px 0 19px; border-left: 1px solid #ddd; }
|
||||
blockquote cite { display: block; font-size: 12px; color: #555; }
|
||||
blockquote cite:before { content: "\2014 \0020"; }
|
||||
blockquote cite a, blockquote cite a:visited, blockquote cite a:visited { color: #555; }
|
||||
|
||||
hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 10px 0 30px; height: 0; }
|
||||
|
||||
|
||||
/* #Links
|
||||
================================================== */
|
||||
a, a:visited { color: #333; text-decoration: underline; outline: 0; }
|
||||
a:hover, a:focus { color: #000; }
|
||||
p a, p a:visited { line-height: inherit; }
|
||||
|
||||
|
||||
/* #Lists
|
||||
================================================== */
|
||||
ul, ol { margin-bottom: 20px; }
|
||||
ul { list-style: none outside; }
|
||||
ol { list-style: decimal; }
|
||||
ol, ul.square, ul.circle, ul.disc { margin-left: 30px; }
|
||||
ul.square { list-style: square outside; }
|
||||
ul.circle { list-style: circle outside; }
|
||||
ul.disc { list-style: disc outside; }
|
||||
ul ul, ul ol,
|
||||
ol ol, ol ul { margin: 4px 0 5px 30px; font-size: 90%; }
|
||||
ul ul li, ul ol li,
|
||||
ol ol li, ol ul li { margin-bottom: 6px; }
|
||||
li { line-height: 18px; margin-bottom: 12px; }
|
||||
ul.large li { line-height: 21px; }
|
||||
li p { line-height: 21px; }
|
||||
|
||||
/* #Images
|
||||
================================================== */
|
||||
|
||||
img.scale-with-grid {
|
||||
max-width: 100%;
|
||||
height: auto; }
|
||||
|
||||
|
||||
/* #Buttons
|
||||
================================================== */
|
||||
|
||||
.button,
|
||||
button,
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"] {
|
||||
background: #eee; /* Old browsers */
|
||||
background: #eee -moz-linear-gradient(top, rgba(255,255,255,.2) 0%, rgba(0,0,0,.2) 100%); /* FF3.6+ */
|
||||
background: #eee -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.2)), color-stop(100%,rgba(0,0,0,.2))); /* Chrome,Safari4+ */
|
||||
background: #eee -webkit-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: #eee -o-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Opera11.10+ */
|
||||
background: #eee -ms-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* IE10+ */
|
||||
background: #eee linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* W3C */
|
||||
border: 1px solid #aaa;
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
color: #444;
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px rgba(255, 255, 255, .75);
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
line-height: normal;
|
||||
padding: 8px 10px;
|
||||
font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; }
|
||||
|
||||
.button:hover,
|
||||
button:hover,
|
||||
input[type="submit"]:hover,
|
||||
input[type="reset"]:hover,
|
||||
input[type="button"]:hover {
|
||||
color: #222;
|
||||
background: #ddd; /* Old browsers */
|
||||
background: #ddd -moz-linear-gradient(top, rgba(255,255,255,.3) 0%, rgba(0,0,0,.3) 100%); /* FF3.6+ */
|
||||
background: #ddd -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.3)), color-stop(100%,rgba(0,0,0,.3))); /* Chrome,Safari4+ */
|
||||
background: #ddd -webkit-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: #ddd -o-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Opera11.10+ */
|
||||
background: #ddd -ms-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* IE10+ */
|
||||
background: #ddd linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* W3C */
|
||||
border: 1px solid #888;
|
||||
border-top: 1px solid #aaa;
|
||||
border-left: 1px solid #aaa; }
|
||||
|
||||
.button:active,
|
||||
button:active,
|
||||
input[type="submit"]:active,
|
||||
input[type="reset"]:active,
|
||||
input[type="button"]:active {
|
||||
border: 1px solid #666;
|
||||
background: #ccc; /* Old browsers */
|
||||
background: #ccc -moz-linear-gradient(top, rgba(255,255,255,.35) 0%, rgba(10,10,10,.4) 100%); /* FF3.6+ */
|
||||
background: #ccc -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.35)), color-stop(100%,rgba(10,10,10,.4))); /* Chrome,Safari4+ */
|
||||
background: #ccc -webkit-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: #ccc -o-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Opera11.10+ */
|
||||
background: #ccc -ms-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* IE10+ */
|
||||
background: #ccc linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* W3C */ }
|
||||
|
||||
.button.full-width,
|
||||
button.full-width,
|
||||
input[type="submit"].full-width,
|
||||
input[type="reset"].full-width,
|
||||
input[type="button"].full-width {
|
||||
width: 100%;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
text-align: center; }
|
||||
|
||||
/* Fix for odd Mozilla border & padding issues */
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/* #Forms
|
||||
================================================== */
|
||||
|
||||
form {
|
||||
margin-bottom: 20px; }
|
||||
fieldset {
|
||||
margin-bottom: 20px; }
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"],
|
||||
textarea,
|
||||
select {
|
||||
border: 1px solid #ccc;
|
||||
padding: 6px 4px;
|
||||
outline: none;
|
||||
-moz-border-radius: 2px;
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
font: 13px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #777;
|
||||
margin: 0;
|
||||
width: 210px;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
background: #fff; }
|
||||
select {
|
||||
padding: 0; }
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="email"]:focus,
|
||||
textarea:focus {
|
||||
border: 1px solid #aaa;
|
||||
color: #444;
|
||||
-moz-box-shadow: 0 0 3px rgba(0,0,0,.2);
|
||||
-webkit-box-shadow: 0 0 3px rgba(0,0,0,.2);
|
||||
box-shadow: 0 0 3px rgba(0,0,0,.2); }
|
||||
textarea {
|
||||
min-height: 60px; }
|
||||
label,
|
||||
legend {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 13px; }
|
||||
select {
|
||||
width: 220px; }
|
||||
input[type="checkbox"] {
|
||||
display: inline; }
|
||||
label span,
|
||||
legend span {
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
color: #444; }
|
||||
|
||||
/* #Misc
|
||||
================================================== */
|
||||
.remove-bottom { margin-bottom: 0 !important; }
|
||||
.half-bottom { margin-bottom: 10px !important; }
|
||||
.add-bottom { margin-bottom: 20px !important; }
|
||||
|
||||
|
47
themes/colorbox/static/css/colorbox.scss
Normal file
47
themes/colorbox/static/css/colorbox.scss
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
ColorBox Core Style:
|
||||
The following CSS is consistent between example themes and should not be altered.
|
||||
*/
|
||||
#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
|
||||
#cboxOverlay{position:fixed; width:100%; height:100%;}
|
||||
#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
|
||||
#cboxContent{position:relative;}
|
||||
#cboxLoadedContent{overflow:auto; -webkit-overflow-scrolling: touch;}
|
||||
#cboxTitle{margin:0;}
|
||||
#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;}
|
||||
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
|
||||
.cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:none;}
|
||||
.cboxIframe{width:100%; height:100%; display:block; border:0;}
|
||||
#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box; -moz-box-sizing:content-box; -webkit-box-sizing:content-box;}
|
||||
|
||||
/*
|
||||
User Style:
|
||||
Change the following styles to modify the appearance of ColorBox. They are
|
||||
ordered & tabbed in a way that represents the nesting of the generated HTML.
|
||||
*/
|
||||
#cboxOverlay{background:#000;}
|
||||
#colorbox{outline:0;}
|
||||
#cboxContent{margin-top:20px;background:#000;}
|
||||
.cboxIframe{background:#fff;}
|
||||
#cboxError{padding:50px; border:1px solid #ccc;}
|
||||
#cboxLoadedContent{border:5px solid #000; background:#fff;}
|
||||
#cboxTitle{position:absolute; top:-20px; left:0; color:#ccc;}
|
||||
#cboxCurrent{position:absolute; top:-20px; right:0px; color:#ccc;}
|
||||
#cboxLoadingGraphic{background:url(../images/loading.gif) no-repeat center center;}
|
||||
|
||||
/* these elements are buttons, and may need to have additional styles reset to avoid unwanted base styles */
|
||||
#cboxPrevious, #cboxNext, #cboxSlideshow, #cboxClose {border:0; padding:0; margin:0; overflow:visible; width:auto; background:none; }
|
||||
|
||||
/* avoid outlines on :active (mouseclick), but preserve outlines on :focus (tabbed navigating) */
|
||||
#cboxPrevious:active, #cboxNext:active, #cboxSlideshow:active, #cboxClose:active {outline:0;}
|
||||
|
||||
#cboxSlideshow{position:absolute; top:-20px; right:90px; color:#fff;}
|
||||
#cboxPrevious{position:absolute; top:50%; left:5px; margin-top:-32px;
|
||||
background:url(../images/controls.png) no-repeat top left; width:28px; height:65px; text-indent:-9999px;}
|
||||
#cboxPrevious:hover{background-position:bottom left;}
|
||||
#cboxNext{position:absolute; top:50%; right:5px; margin-top:-32px;
|
||||
background:url(../images/controls.png) no-repeat top right; width:28px; height:65px; text-indent:-9999px;}
|
||||
#cboxNext:hover{background-position:bottom right;}
|
||||
#cboxClose{position:absolute; top:5px; right:5px; display:block;
|
||||
background:url(../images/controls.png) no-repeat top center; width:38px; height:19px; text-indent:-9999px;}
|
||||
#cboxClose:hover{background-position:bottom center;}
|
58
themes/colorbox/static/css/layout.scss
Normal file
58
themes/colorbox/static/css/layout.scss
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Skeleton V1.2
|
||||
* Copyright 2011, Dave Gamache
|
||||
* www.getskeleton.com
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* 6/20/2012
|
||||
*/
|
||||
|
||||
/* Table of Content
|
||||
==================================================
|
||||
#Site Styles
|
||||
#Page Styles
|
||||
#Media Queries
|
||||
#Font-Face */
|
||||
|
||||
/* #Site Styles
|
||||
================================================== */
|
||||
|
||||
/* #Page Styles
|
||||
================================================== */
|
||||
|
||||
/* #Media Queries
|
||||
================================================== */
|
||||
|
||||
/* Smaller than standard 960 (devices and browsers) */
|
||||
@media only screen and (max-width: 959px) {}
|
||||
|
||||
/* Tablet Portrait size to standard 960 (devices and browsers) */
|
||||
@media only screen and (min-width: 768px) and (max-width: 959px) {}
|
||||
|
||||
/* All Mobile Sizes (devices and browser) */
|
||||
@media only screen and (max-width: 767px) {}
|
||||
|
||||
/* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
|
||||
@media only screen and (min-width: 480px) and (max-width: 767px) {}
|
||||
|
||||
/* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
|
||||
@media only screen and (max-width: 479px) {}
|
||||
|
||||
|
||||
/* #Font-Face
|
||||
================================================== */
|
||||
/* This is the proper syntax for an @font-face file
|
||||
Just create a "fonts" folder at the root,
|
||||
copy your FontName into code below and remove
|
||||
comment brackets */
|
||||
|
||||
/* @font-face {
|
||||
font-family: 'FontName';
|
||||
src: url('../fonts/FontName.eot');
|
||||
src: url('../fonts/FontName.eot?iefix') format('eot'),
|
||||
url('../fonts/FontName.woff') format('woff'),
|
||||
url('../fonts/FontName.ttf') format('truetype'),
|
||||
url('../fonts/FontName.svg#webfontZam02nTh') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal; }
|
||||
*/
|
242
themes/colorbox/static/css/skeleton.scss
vendored
Normal file
242
themes/colorbox/static/css/skeleton.scss
vendored
Normal file
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* Skeleton V1.2
|
||||
* Copyright 2011, Dave Gamache
|
||||
* www.getskeleton.com
|
||||
* Free to use under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* 6/20/2012
|
||||
*/
|
||||
|
||||
|
||||
/* Table of Contents
|
||||
==================================================
|
||||
#Base 960 Grid
|
||||
#Tablet (Portrait)
|
||||
#Mobile (Portrait)
|
||||
#Mobile (Landscape)
|
||||
#Clearing */
|
||||
|
||||
|
||||
|
||||
/* #Base 960 Grid
|
||||
================================================== */
|
||||
|
||||
.container { position: relative; width: 960px; margin: 0 auto; padding: 0; }
|
||||
.container .column,
|
||||
.container .columns { float: left; display: inline; margin-left: 10px; margin-right: 10px; }
|
||||
.row { margin-bottom: 20px; }
|
||||
|
||||
/* Nested Column Classes */
|
||||
.column.alpha, .columns.alpha { margin-left: 0; }
|
||||
.column.omega, .columns.omega { margin-right: 0; }
|
||||
|
||||
/* Base Grid */
|
||||
.container .one.column,
|
||||
.container .one.columns { width: 40px; }
|
||||
.container .two.columns { width: 100px; }
|
||||
.container .three.columns { width: 160px; }
|
||||
.container .four.columns { width: 220px; }
|
||||
.container .five.columns { width: 280px; }
|
||||
.container .six.columns { width: 340px; }
|
||||
.container .seven.columns { width: 400px; }
|
||||
.container .eight.columns { width: 460px; }
|
||||
.container .nine.columns { width: 520px; }
|
||||
.container .ten.columns { width: 580px; }
|
||||
.container .eleven.columns { width: 640px; }
|
||||
.container .twelve.columns { width: 700px; }
|
||||
.container .thirteen.columns { width: 760px; }
|
||||
.container .fourteen.columns { width: 820px; }
|
||||
.container .fifteen.columns { width: 880px; }
|
||||
.container .sixteen.columns { width: 940px; }
|
||||
|
||||
.container .one-third.column { width: 300px; }
|
||||
.container .two-thirds.column { width: 620px; }
|
||||
|
||||
/* Offsets */
|
||||
.container .offset-by-one { padding-left: 60px; }
|
||||
.container .offset-by-two { padding-left: 120px; }
|
||||
.container .offset-by-three { padding-left: 180px; }
|
||||
.container .offset-by-four { padding-left: 240px; }
|
||||
.container .offset-by-five { padding-left: 300px; }
|
||||
.container .offset-by-six { padding-left: 360px; }
|
||||
.container .offset-by-seven { padding-left: 420px; }
|
||||
.container .offset-by-eight { padding-left: 480px; }
|
||||
.container .offset-by-nine { padding-left: 540px; }
|
||||
.container .offset-by-ten { padding-left: 600px; }
|
||||
.container .offset-by-eleven { padding-left: 660px; }
|
||||
.container .offset-by-twelve { padding-left: 720px; }
|
||||
.container .offset-by-thirteen { padding-left: 780px; }
|
||||
.container .offset-by-fourteen { padding-left: 840px; }
|
||||
.container .offset-by-fifteen { padding-left: 900px; }
|
||||
|
||||
|
||||
|
||||
/* #Tablet (Portrait)
|
||||
================================================== */
|
||||
|
||||
/* Note: Design for a width of 768px */
|
||||
|
||||
@media only screen and (min-width: 768px) and (max-width: 959px) {
|
||||
.container { width: 768px; }
|
||||
.container .column,
|
||||
.container .columns { margin-left: 10px; margin-right: 10px; }
|
||||
.column.alpha, .columns.alpha { margin-left: 0; margin-right: 10px; }
|
||||
.column.omega, .columns.omega { margin-right: 0; margin-left: 10px; }
|
||||
.alpha.omega { margin-left: 0; margin-right: 0; }
|
||||
|
||||
.container .one.column,
|
||||
.container .one.columns { width: 28px; }
|
||||
.container .two.columns { width: 76px; }
|
||||
.container .three.columns { width: 124px; }
|
||||
.container .four.columns { width: 172px; }
|
||||
.container .five.columns { width: 220px; }
|
||||
.container .six.columns { width: 268px; }
|
||||
.container .seven.columns { width: 316px; }
|
||||
.container .eight.columns { width: 364px; }
|
||||
.container .nine.columns { width: 412px; }
|
||||
.container .ten.columns { width: 460px; }
|
||||
.container .eleven.columns { width: 508px; }
|
||||
.container .twelve.columns { width: 556px; }
|
||||
.container .thirteen.columns { width: 604px; }
|
||||
.container .fourteen.columns { width: 652px; }
|
||||
.container .fifteen.columns { width: 700px; }
|
||||
.container .sixteen.columns { width: 748px; }
|
||||
|
||||
.container .one-third.column { width: 236px; }
|
||||
.container .two-thirds.column { width: 492px; }
|
||||
|
||||
/* Offsets */
|
||||
.container .offset-by-one { padding-left: 48px; }
|
||||
.container .offset-by-two { padding-left: 96px; }
|
||||
.container .offset-by-three { padding-left: 144px; }
|
||||
.container .offset-by-four { padding-left: 192px; }
|
||||
.container .offset-by-five { padding-left: 240px; }
|
||||
.container .offset-by-six { padding-left: 288px; }
|
||||
.container .offset-by-seven { padding-left: 336px; }
|
||||
.container .offset-by-eight { padding-left: 384px; }
|
||||
.container .offset-by-nine { padding-left: 432px; }
|
||||
.container .offset-by-ten { padding-left: 480px; }
|
||||
.container .offset-by-eleven { padding-left: 528px; }
|
||||
.container .offset-by-twelve { padding-left: 576px; }
|
||||
.container .offset-by-thirteen { padding-left: 624px; }
|
||||
.container .offset-by-fourteen { padding-left: 672px; }
|
||||
.container .offset-by-fifteen { padding-left: 720px; }
|
||||
}
|
||||
|
||||
|
||||
/* #Mobile (Portrait)
|
||||
================================================== */
|
||||
|
||||
/* Note: Design for a width of 320px */
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.container { width: 300px; }
|
||||
.container .columns,
|
||||
.container .column { margin: 0; }
|
||||
|
||||
.container .one.column,
|
||||
.container .one.columns,
|
||||
.container .two.columns,
|
||||
.container .three.columns,
|
||||
.container .four.columns,
|
||||
.container .five.columns,
|
||||
.container .six.columns,
|
||||
.container .seven.columns,
|
||||
.container .eight.columns,
|
||||
.container .nine.columns,
|
||||
.container .ten.columns,
|
||||
.container .eleven.columns,
|
||||
.container .twelve.columns,
|
||||
.container .thirteen.columns,
|
||||
.container .fourteen.columns,
|
||||
.container .fifteen.columns,
|
||||
.container .sixteen.columns,
|
||||
.container .one-third.column,
|
||||
.container .two-thirds.column { width: 300px; }
|
||||
|
||||
/* Offsets */
|
||||
.container .offset-by-one,
|
||||
.container .offset-by-two,
|
||||
.container .offset-by-three,
|
||||
.container .offset-by-four,
|
||||
.container .offset-by-five,
|
||||
.container .offset-by-six,
|
||||
.container .offset-by-seven,
|
||||
.container .offset-by-eight,
|
||||
.container .offset-by-nine,
|
||||
.container .offset-by-ten,
|
||||
.container .offset-by-eleven,
|
||||
.container .offset-by-twelve,
|
||||
.container .offset-by-thirteen,
|
||||
.container .offset-by-fourteen,
|
||||
.container .offset-by-fifteen { padding-left: 0; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* #Mobile (Landscape)
|
||||
================================================== */
|
||||
|
||||
/* Note: Design for a width of 480px */
|
||||
|
||||
@media only screen and (min-width: 480px) and (max-width: 767px) {
|
||||
.container { width: 420px; }
|
||||
.container .columns,
|
||||
.container .column { margin: 0; }
|
||||
|
||||
.container .one.column,
|
||||
.container .one.columns,
|
||||
.container .two.columns,
|
||||
.container .three.columns,
|
||||
.container .four.columns,
|
||||
.container .five.columns,
|
||||
.container .six.columns,
|
||||
.container .seven.columns,
|
||||
.container .eight.columns,
|
||||
.container .nine.columns,
|
||||
.container .ten.columns,
|
||||
.container .eleven.columns,
|
||||
.container .twelve.columns,
|
||||
.container .thirteen.columns,
|
||||
.container .fourteen.columns,
|
||||
.container .fifteen.columns,
|
||||
.container .sixteen.columns,
|
||||
.container .one-third.column,
|
||||
.container .two-thirds.column { width: 420px; }
|
||||
}
|
||||
|
||||
|
||||
/* #Clearing
|
||||
================================================== */
|
||||
|
||||
/* Self Clearing Goodness */
|
||||
.container:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; }
|
||||
|
||||
/* Use clearfix class on parent to clear nested columns,
|
||||
or wrap each row of columns in a <div class="row"> */
|
||||
.clearfix:before,
|
||||
.clearfix:after,
|
||||
.row:before,
|
||||
.row:after {
|
||||
content: '\0020';
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
height: 0; }
|
||||
.row:after,
|
||||
.clearfix:after {
|
||||
clear: both; }
|
||||
.row,
|
||||
.clearfix {
|
||||
zoom: 1; }
|
||||
|
||||
/* You can also use a <br class="clear" /> to clear columns */
|
||||
.clear {
|
||||
clear: both;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
1
themes/colorbox/static/css/style.min.css
vendored
Normal file
1
themes/colorbox/static/css/style.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
98
themes/colorbox/static/css/style.scss
Normal file
98
themes/colorbox/static/css/style.scss
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* -*- scss-compile-at-save: nil -*- */
|
||||
|
||||
@import "base";
|
||||
@import "skeleton";
|
||||
@import "colorbox";
|
||||
|
||||
body {
|
||||
font: 100%/1.4 "PT Sans", sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "PT Sans", sans-serif;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc outside;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 0 0 40px;
|
||||
}
|
||||
|
||||
header {
|
||||
h1 a { text-decoration: none; }
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
line-height: 40px;
|
||||
margin: 40px 0;
|
||||
|
||||
a { text-decoration: none; }
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 0 5px 5px 0;
|
||||
border: 10px solid #fff;
|
||||
border-radius: 2px 2px 2px 2px;
|
||||
box-shadow: 0 0 3px #B1B1B1;
|
||||
padding: 0 0 2px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 5px #818181;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.album_thumb {}
|
||||
.album_title {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#cboxTitle {
|
||||
max-width: 90%;
|
||||
a, a:visited {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 767px) {
|
||||
header {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.container {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
.sidebar footer {
|
||||
bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) and (max-width: 959px) {
|
||||
.sidebar {
|
||||
width: 172px;
|
||||
}
|
||||
}
|
BIN
themes/colorbox/static/images/controls.png
Normal file
BIN
themes/colorbox/static/images/controls.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
themes/colorbox/static/images/loading.gif
Normal file
BIN
themes/colorbox/static/images/loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
6
themes/colorbox/static/js/jquery-1.10.2.min.js
vendored
Normal file
6
themes/colorbox/static/js/jquery-1.10.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
themes/colorbox/static/js/jquery.colorbox.min.js
vendored
Normal file
7
themes/colorbox/static/js/jquery.colorbox.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
130
themes/colorbox/templates/album.html
Normal file
130
themes/colorbox/templates/album.html
Normal file
|
@ -0,0 +1,130 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<link rel="stylesheet" href="{{ SIGAL_THEME_URL }}/css/style.min.css"> <!-- this needs to be in head -->
|
||||
<div id="main" role="main" class="twelve columns offset-by-four">
|
||||
<header>
|
||||
{% if SIGAL_ALBUM.breadcrumb %}
|
||||
<h2>
|
||||
{% for url, title in SIGAL_ALBUM.breadcrumb %}
|
||||
<a href="{{ url }}">{{ title }}</a>{% if not loop.last %} » {% endif %}
|
||||
{% endfor -%}
|
||||
</h2>
|
||||
<hr>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
{% set numbers = ["zero", "one", "two", "three", "four", "five", "six",
|
||||
"seven", "eight", "nine", "ten", "eleven", "twelve"] %}
|
||||
{% set column_size = SIGAL_COLORBOX_COLUMN_SIZE %}
|
||||
{% set nb_columns = (12 / column_size)|int %}
|
||||
{% set column_size_t = numbers[column_size] %}
|
||||
|
||||
{% if SIGAL_ALBUM.albums %}
|
||||
{% for alb in SIGAL_ALBUM.albums %}
|
||||
{% if loop.index % nb_columns == 1 %}
|
||||
<div id="albums" class="row">
|
||||
{% endif%}
|
||||
<div class="{{ column_size_t }} columns thumbnail
|
||||
{% if loop.index % nb_columns == 1 %}alpha{% endif%}
|
||||
{% if loop.index % nb_columns == 0 %}omega{% endif%}">
|
||||
<a href="{{ alb.url }}">
|
||||
<img src="{{ alb.thumbnail }}" class="album_thumb"
|
||||
alt="{{ alb.name }}" title="{{ alb.name }}" /></a>
|
||||
<span class="album_title">{{ alb.title }}</span>
|
||||
</div>
|
||||
{% if loop.last or loop.index % nb_columns == 0 %}
|
||||
</div>
|
||||
{% endif%}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if SIGAL_ALBUM.medias %}
|
||||
{% macro img_description(media) -%}
|
||||
{% if media.big %} data-big="{{ media.big }}"{% endif %}
|
||||
{% if media.exif %}
|
||||
{% if media.exif.datetime %}
|
||||
data-date=", {{ media.exif.datetime }}"
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
<div id="gallery" class="row">
|
||||
{% for media in SIGAL_ALBUM.medias %}
|
||||
{% if media.type == "image" %}
|
||||
<div class="{{ column_size_t }} columns thumbnail
|
||||
{% if loop.index % nb_columns == 1 %}alpha{% endif%}
|
||||
{% if loop.index % nb_columns == 0 %}omega{% endif%}">
|
||||
<a href="{{ media.filename }}" class="gallery" title="{{ media.filename }}" {{ img_description(media) }}>
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
title="{{ media.title if media.title else media.filename }}" /></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if media.type == "video" %}
|
||||
<div class="{{ column_size_t }} columns thumbnail
|
||||
{% if loop.index % nb_columns == 1 %}alpha{% endif%}
|
||||
{% if loop.index % nb_columns == 0 %}omega{% endif%}">
|
||||
<a href="#{{ media.filename|replace('.', '')|replace(' ', '') }}"
|
||||
class="gallery" inline='yes' title="{{ media.filename }}"
|
||||
{% if media.big %} data-big="{{ media.big }}"{% endif %}>
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
title="{{ media.title if media.title else media.filename }}" /></a>
|
||||
</div>
|
||||
<!-- This contains the hidden content for the video -->
|
||||
<div style='display:none'>
|
||||
<div id="{{ media.filename|replace('.', '')|replace(' ', '') }}">
|
||||
<video controls>
|
||||
<source src='{{ media.filename }}' type='video/webm' />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if SIGAL_ALBUM.zip %}
|
||||
<div id="additionnal-infos" class="row">
|
||||
<p><a href="{{ album.zip }}"
|
||||
title="Download a zip archive with all images">Download ZIP</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if SIGAL_ALBUM.description %}
|
||||
<div id="description" class="row">
|
||||
{{ SIGAL_ALBUM.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if SIGAL_ALBUM.medias %}
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script>!window.jQuery && document.write(unescape('%3Cscript src="{{ SIGAL_THEME_URL }}/js/jquery-1.10.2.min.js"%3E%3C/script%3E'))</script>
|
||||
<script src="{{ SIGAL_THEME_URL }}/js/jquery.colorbox.min.js"></script>
|
||||
|
||||
<script>
|
||||
$(".gallery").colorbox({
|
||||
rel:"gallery",
|
||||
transition:"none",
|
||||
maxWidth: "90%",
|
||||
maxHeight: "90%",
|
||||
scalePhotos: true,
|
||||
current: "{current} / {total}",
|
||||
title: function () {
|
||||
title = this.title;
|
||||
if(this.hasAttribute("data-big")) {
|
||||
title += " (full size)".link(this.getAttribute("data-big"));
|
||||
}
|
||||
if(this.hasAttribute("data-date")) {
|
||||
title += this.getAttribute("data-date");
|
||||
}
|
||||
return title;
|
||||
},
|
||||
inline: function() {
|
||||
return this.hasAttribute("inline");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
165
themes/colorbox/templates/index.html.bak
Normal file
165
themes/colorbox/templates/index.html.bak
Normal file
|
@ -0,0 +1,165 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
<title>{{ album.title }}</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="{{ author }}">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=PT+Sans">
|
||||
<link rel="stylesheet" href="{{ theme.url }}/css/style.min.css">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="four columns">
|
||||
<div class="sidebar">
|
||||
<h1><a href="{{ album.index_url }}">{{ index_title }}</a></h1>
|
||||
{% if settings.links %}
|
||||
<nav id="menu">
|
||||
<ul>
|
||||
{% for title, link in settings.links %}
|
||||
<li><a href="{{ link }}">{{ title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
<footer>
|
||||
<p>{% if author %}© {{ author }} - {% endif %}
|
||||
Generated by <a href="{{ sigal_link }}">sigal</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="main" role="main" class="twelve columns offset-by-four">
|
||||
<header>
|
||||
{% if album.breadcrumb %}
|
||||
<h2>
|
||||
{% for url, title in album.breadcrumb %}
|
||||
<a href="{{ url }}">{{ title }}</a>{% if not loop.last %} » {% endif %}
|
||||
{% endfor -%}
|
||||
</h2>
|
||||
<hr>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
{% set numbers = ["zero", "one", "two", "three", "four", "five", "six",
|
||||
"seven", "eight", "nine", "ten", "eleven", "twelve"] %}
|
||||
{% set column_size = settings.colorbox_column_size %}
|
||||
{% set nb_columns = (12 / column_size)|int %}
|
||||
{% set column_size_t = numbers[column_size] %}
|
||||
|
||||
{% if album.albums %}
|
||||
{% for alb in album.albums %}
|
||||
{% if loop.index % nb_columns == 1 %}
|
||||
<div id="albums" class="row">
|
||||
{% endif%}
|
||||
<div class="{{ column_size_t }} columns thumbnail
|
||||
{% if loop.index % nb_columns == 1 %}alpha{% endif%}
|
||||
{% if loop.index % nb_columns == 0 %}omega{% endif%}">
|
||||
<a href="{{ alb.url }}">
|
||||
<img src="{{ alb.thumbnail }}" class="album_thumb"
|
||||
alt="{{ alb.name }}" title="{{ alb.name }}" /></a>
|
||||
<span class="album_title">{{ alb.title }}</span>
|
||||
</div>
|
||||
{% if loop.last or loop.index % nb_columns == 0 %}
|
||||
</div>
|
||||
{% endif%}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if album.medias %}
|
||||
{% macro img_description(media) -%}
|
||||
{% if media.big %} data-big="{{ media.big }}"{% endif %}
|
||||
{% if media.exif %}
|
||||
{% if media.exif.datetime %}
|
||||
data-date=", {{ media.exif.datetime }}"
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
<div id="gallery" class="row">
|
||||
{% for media in album.medias %}
|
||||
{% if media.type == "image" %}
|
||||
<div class="{{ column_size_t }} columns thumbnail
|
||||
{% if loop.index % nb_columns == 1 %}alpha{% endif%}
|
||||
{% if loop.index % nb_columns == 0 %}omega{% endif%}">
|
||||
<a href="{{ media.filename }}" class="gallery" title="{{ media.filename }}" {{ img_description(media) }}>
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
title="{{ media.title if media.title else media.filename }}" /></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if media.type == "video" %}
|
||||
<div class="{{ column_size_t }} columns thumbnail
|
||||
{% if loop.index % nb_columns == 1 %}alpha{% endif%}
|
||||
{% if loop.index % nb_columns == 0 %}omega{% endif%}">
|
||||
<a href="#{{ media.filename|replace('.', '')|replace(' ', '') }}"
|
||||
class="gallery" inline='yes' title="{{ media.filename }}"
|
||||
{% if media.big %} data-big="{{ media.big }}"{% endif %}>
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
title="{{ media.title if media.title else media.filename }}" /></a>
|
||||
</div>
|
||||
<!-- This contains the hidden content for the video -->
|
||||
<div style='display:none'>
|
||||
<div id="{{ media.filename|replace('.', '')|replace(' ', '') }}">
|
||||
<video controls>
|
||||
<source src='{{ media.filename }}' type='video/webm' />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if album.zip %}
|
||||
<div id="additionnal-infos" class="row">
|
||||
<p><a href="{{ album.zip }}"
|
||||
title="Download a zip archive with all images">Download ZIP</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if album.description %}
|
||||
<div id="description" class="row">
|
||||
{{ album.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if album.medias %}
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script>!window.jQuery && document.write(unescape('%3Cscript src="{{ theme.url }}/js/jquery-1.10.2.min.js"%3E%3C/script%3E'))</script>
|
||||
<script src="{{ theme.url }}/js/jquery.colorbox.min.js"></script>
|
||||
|
||||
<script>
|
||||
$(".gallery").colorbox({
|
||||
rel:"gallery",
|
||||
transition:"none",
|
||||
maxWidth: "90%",
|
||||
maxHeight: "90%",
|
||||
scalePhotos: true,
|
||||
current: "{current} / {total}",
|
||||
title: function () {
|
||||
title = this.title;
|
||||
if(this.hasAttribute("data-big")) {
|
||||
title += " (full size)".link(this.getAttribute("data-big"));
|
||||
}
|
||||
if(this.hasAttribute("data-date")) {
|
||||
title += this.getAttribute("data-date");
|
||||
}
|
||||
return title;
|
||||
},
|
||||
inline: function() {
|
||||
return this.hasAttribute("inline");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% include 'analytics.html' %}
|
||||
</body>
|
||||
</html>
|
13
themes/default/templates/analytics.html
Normal file
13
themes/default/templates/analytics.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% if settings.google_analytics %}
|
||||
<script>
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '{{ settings.google_analytics }}']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
222
themes/galleria/static/css/galleria.classic.css
Normal file
222
themes/galleria/static/css/galleria.classic.css
Normal file
|
@ -0,0 +1,222 @@
|
|||
/* Galleria Classic Theme 2012-08-07 | https://raw.github.com/aino/galleria/master/LICENSE | (c) Aino */
|
||||
|
||||
#galleria-loader{height:1px!important}
|
||||
|
||||
.galleria-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.galleria-container img {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-o-user-select: none;
|
||||
}
|
||||
.galleria-stage {
|
||||
background: #222;
|
||||
background: rgba( 0, 0, 0, .15 );
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 80px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
overflow:hidden;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.galleria-thumbnails-container {
|
||||
height: 70px;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
z-index: 2;
|
||||
}
|
||||
.galleria-carousel .galleria-thumbnails-list {
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
.galleria-thumbnails .galleria-image {
|
||||
height: 60px;
|
||||
width: 90px;
|
||||
background: transparent;
|
||||
margin: 0 10px 0 0;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.galleria-counter {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
text-align: right;
|
||||
z-index: 2;
|
||||
}
|
||||
.galleria-loader {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
background: url('../img/classic-loader.gif') no-repeat 2px 2px;
|
||||
}
|
||||
.galleria-info {
|
||||
width: 50%;
|
||||
top: 15px;
|
||||
left: 25px;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
}
|
||||
.galleria-info-text {
|
||||
background: #000;
|
||||
background: rgba(0, 0, 0, .4);
|
||||
padding: 15px;
|
||||
display: none;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.galleria-info-title {
|
||||
font-size: 1.4em;
|
||||
line-height: 1.4em;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
}
|
||||
.galleria-info-description {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.2em;
|
||||
margin: 0;
|
||||
color: #bbb;
|
||||
}
|
||||
.galleria-info-title+.galleria-info-description { margin-top: 7px; }
|
||||
.galleria-info-close {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background-position: -753px -11px;
|
||||
opacity: .5;
|
||||
filter: alpha(opacity=50);
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
.notouch .galleria-info-close:hover{
|
||||
opacity:1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.touch .galleria-info-close:active{
|
||||
opacity:1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.galleria-info-link {
|
||||
background-position: -669px -5px;
|
||||
opacity: .7;
|
||||
filter: alpha(opacity=70);
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.notouch .galleria-info-link:hover {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.touch .galleria-info-link:active {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.galleria-image-nav {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -62px;
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
left: 0;
|
||||
}
|
||||
.galleria-image-nav-left,
|
||||
.galleria-image-nav-right {
|
||||
opacity: .3;
|
||||
filter: alpha(opacity=30);
|
||||
cursor: pointer;
|
||||
width: 62px;
|
||||
height: 124px;
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
z-index: 2;
|
||||
background-position: 0 46px;
|
||||
}
|
||||
.galleria-image-nav-right {
|
||||
left: auto;
|
||||
right: 15px;
|
||||
background-position: -254px 46px;
|
||||
z-index: 2;
|
||||
}
|
||||
.notouch .galleria-image-nav-left:hover,
|
||||
.notouch .galleria-image-nav-right:hover {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.touch .galleria-image-nav-left:active,
|
||||
.touch .galleria-image-nav-right:active {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.galleria-thumb-nav-left,
|
||||
.galleria-thumb-nav-right {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
background-position: -495px 5px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 40px;
|
||||
width: 23px;
|
||||
z-index: 3;
|
||||
opacity: .8;
|
||||
filter: alpha(opacity=80);
|
||||
}
|
||||
.galleria-thumb-nav-right {
|
||||
background-position: -578px 5px;
|
||||
border-right: none;
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
.galleria-thumbnails-container .disabled {
|
||||
opacity: .2;
|
||||
filter: alpha(opacity=20);
|
||||
cursor: default;
|
||||
}
|
||||
.notouch .galleria-thumb-nav-left:hover,
|
||||
.notouch .galleria-thumb-nav-right:hover {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
background-color: #111;
|
||||
}
|
||||
.touch .galleria-thumb-nav-left:active,
|
||||
.touch .galleria-thumb-nav-right:active {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
background-color: #111;
|
||||
}
|
||||
.notouch .galleria-thumbnails-container .disabled:hover {
|
||||
opacity: .2;
|
||||
filter: alpha(opacity=20);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.galleria-carousel .galleria-thumb-nav-left,
|
||||
.galleria-carousel .galleria-thumb-nav-right {
|
||||
display: block;
|
||||
}
|
||||
.galleria-thumb-nav-left,
|
||||
.galleria-thumb-nav-right,
|
||||
.galleria-info-link,
|
||||
.galleria-info-close,
|
||||
.galleria-image-nav-left,
|
||||
.galleria-image-nav-right {
|
||||
background-image: url('../img/classic-map.png');
|
||||
background-repeat: no-repeat;
|
||||
}
|
425
themes/galleria/static/css/normalize.scss
vendored
Normal file
425
themes/galleria/static/css/normalize.scss
vendored
Normal file
|
@ -0,0 +1,425 @@
|
|||
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.
|
||||
* Correct `block` display not defined for `main` in IE 11.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9/10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow not hidden in IE 9/10/11.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contain overflow in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address odd `em`-unit font size rendering in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||
* styling of `select`, unless a `border` property is set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited.
|
||||
* Known issue: affects color of disabled elements.
|
||||
* 2. Correct font properties not being inherited.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||
* Correct `select` style inheritance in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's recommended that you don't attempt to style these elements.
|
||||
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
||||
*
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
||||
* `font-size` values of the `input`, it causes the cursor style of the
|
||||
* decrement button to change from `default` to `text`.
|
||||
*/
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||
* padding (and `textfield` appearance).
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't inherit the `font-weight` (applied by a rule above).
|
||||
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
||||
*/
|
||||
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
1
themes/galleria/static/css/style.min.css
vendored
Normal file
1
themes/galleria/static/css/style.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}body{font:16px/1.5 Molengo, sans-serif;background-color:#242424;color:#aaa;text-shadow:0 1px 3px rgba(0,0,0,0.7)}.container{margin:0 auto;text-align:left;width:96%}a,a:link,a:visited{color:#999;text-decoration:underline}a:hover{color:#dadada}header{padding:1em 0;color:#eee}header h1,header h2{color:#eee}header h1 a,header h1 a:link,header h1 a:visited,header h2 a,header h2 a:link,header h2 a:visited{color:#eee;text-shadow:0 2px 0 #000;text-decoration:none}header h1 a:hover,header h2 a:hover{color:#fff;text-decoration:none}header h1,header h2{margin:10px 0}header #menu ul{list-style-type:none;margin:0 0 10px;padding:0}header #menu ul li{display:inline-block}header #menu ul li a,header #menu ul li a:link,header #menu ul li a:visited{color:#aaa;border-bottom:1px solid #aaa;padding-bottom:2px;margin-left:5px;text-decoration:none}header #menu ul li a:hover{color:#eee;border-color:#eee;text-decoration:none}#albums ul{list-style-type:none;padding-left:0}#albums ul li{display:inline-block;margin:0 55px 30px 0;text-align:center;vertical-align:top;width:280px}#albums ul li:nth-child(3n+3){margin-right:0}#albums ul li a img{opacity:1;-webkit-transition:opacity 0.2s ease-in;-moz-transition-property:opacity;-moz-transition-duration:0.2s;-moz-box-shadow:0 1px 6px rgba(0,0,0,0.6);-webkit-box-shadow:0 1px 6px rgba(0,0,0,0.6);-o-box-shadow:0 1px 6px rgba(0,0,0,0.6);box-shadow:0 1px 6px rgba(0,0,0,0.6)}#albums ul li a:hover img{opacity:.5}.album_title{display:block;color:#eee;font-size:1.3em;font-variant:small-caps;font-weight:bold}#gallery{line-height:0;width:100%;height:600px}#gallery video{position:absolute;top:10%;width:100%;margin:0 auto}footer{clear:both;display:block;margin:1em 0;text-align:center}footer a:link,footer a:visited{font-weight:bold;text-decoration:none}footer a:hover{border-bottom:1px solid;text-decoration:none}@media only screen and (min-width: 980px){.container{width:960px}#gallery{width:980px;margin:0 0 40px -10px}header h1,header #menu{display:inline-block;width:49.5%}header #menu{text-align:right}}
|
174
themes/galleria/static/css/style.scss
Normal file
174
themes/galleria/static/css/style.scss
Normal file
|
@ -0,0 +1,174 @@
|
|||
/* -*- scss-compile-at-save: nil -*- */
|
||||
|
||||
@import "normalize";
|
||||
|
||||
$textColor: #aaa;
|
||||
$titleColor: #eee;
|
||||
$linkColor: #999;
|
||||
|
||||
body {
|
||||
font: 16px/1.5 Molengo, sans-serif;
|
||||
background-color: #242424;
|
||||
color: $textColor;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
width: 96%;
|
||||
}
|
||||
|
||||
a, a:link, a:visited {
|
||||
color: $linkColor;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
color: #dadada;
|
||||
}
|
||||
|
||||
/* header */
|
||||
|
||||
header {
|
||||
padding: 1em 0;
|
||||
color: $titleColor;
|
||||
|
||||
h1, h2 {
|
||||
color: $titleColor;
|
||||
|
||||
a, a:link, a:visited {
|
||||
color: $titleColor;
|
||||
text-shadow: 0 2px 0 #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#menu {
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0 0 10px;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
|
||||
a, a:link, a:visited {
|
||||
color: $textColor;
|
||||
border-bottom: 1px solid $textColor;
|
||||
padding-bottom: 2px;
|
||||
margin-left: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: $titleColor;
|
||||
border-color: $titleColor;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* gallery */
|
||||
|
||||
#albums ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 55px 30px 0;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
width: 280px;
|
||||
}
|
||||
li:nth-child(3n+3) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
li a img {
|
||||
opacity: 1;
|
||||
-webkit-transition: opacity 0.2s ease-in;
|
||||
-moz-transition-property: opacity;
|
||||
-moz-transition-duration: 0.2s;
|
||||
-moz-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
|
||||
-webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
|
||||
-o-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
li a:hover img {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.album_thumb {}
|
||||
.album_title {
|
||||
display: block;
|
||||
color: $titleColor;
|
||||
font-size: 1.3em;
|
||||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* galleria */
|
||||
|
||||
#gallery {
|
||||
line-height: 0;
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
|
||||
video {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* footer */
|
||||
|
||||
footer {
|
||||
clear: both;
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
text-align: center;
|
||||
|
||||
a:link, a:visited {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
border-bottom: 1px solid;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (min-width: 980px) {
|
||||
.container {
|
||||
width: 960px;
|
||||
}
|
||||
|
||||
#gallery {
|
||||
width: 980px;
|
||||
margin: 0 0 40px -10px;
|
||||
}
|
||||
|
||||
header {
|
||||
h1, #menu {
|
||||
display: inline-block;
|
||||
width: 49.5%;
|
||||
}
|
||||
#menu {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
BIN
themes/galleria/static/img/classic-loader.gif
Normal file
BIN
themes/galleria/static/img/classic-loader.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
themes/galleria/static/img/classic-map.png
Normal file
BIN
themes/galleria/static/img/classic-map.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
themes/galleria/static/img/empty.png
Normal file
BIN
themes/galleria/static/img/empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 151 B |
3
themes/galleria/static/js/galleria-1.3.5.min.js
vendored
Normal file
3
themes/galleria/static/js/galleria-1.3.5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
themes/galleria/static/js/galleria.classic.min.js
vendored
Normal file
1
themes/galleria/static/js/galleria.classic.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
!function(i){Galleria.addTheme({name:"classic",author:"Galleria",css:"galleria.classic.css",defaults:{transition:"slide",thumbCrop:"height",_toggleInfo:true},init:function(t){Galleria.requires(1.33,"This version of Classic theme requires Galleria 1.3.3 or later");this.addElement("info-link","info-close");this.append({info:["info-link","info-close"]});var e=this.$("info-link,info-close,info-text"),s=Galleria.TOUCH;this.$("loader,counter").show().css("opacity",.4);if(!s){this.addIdleState(this.get("image-nav-left"),{left:-50});this.addIdleState(this.get("image-nav-right"),{right:-50});this.addIdleState(this.get("counter"),{opacity:0})}if(t._toggleInfo===true){e.bind("click:fast",function(){e.toggle()})}else{e.show();this.$("info-link, info-close").hide()}this.bind("thumbnail",function(t){if(!s){i(t.thumbTarget).css("opacity",.6).parent().hover(function(){i(this).not(".active").children().stop().fadeTo(100,1)},function(){i(this).not(".active").children().stop().fadeTo(400,.6)});if(t.index===this.getIndex()){i(t.thumbTarget).css("opacity",1)}}else{i(t.thumbTarget).css("opacity",this.getIndex()?1:.6).bind("click:fast",function(){i(this).css("opacity",1).parent().siblings().children().css("opacity",.6)})}});var n=function(t){i(t.thumbTarget).css("opacity",1).parent().siblings().children().css("opacity",.6)};this.bind("loadstart",function(i){if(!i.cached){this.$("loader").show().fadeTo(200,.4)}window.setTimeout(function(){n(i)},s?300:0);this.$("info").toggle(this.hasInfo())});this.bind("loadfinish",function(i){this.$("loader").fadeOut(200)})}})}(jQuery);
|
1
themes/galleria/static/js/galleria.history.min.js
vendored
Normal file
1
themes/galleria/static/js/galleria.history.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
!function(n,e){Galleria.requires(1.25,"The History Plugin requires Galleria version 1.2.5 or later.");Galleria.History=function(){var i=[],t=false,a=e.location,o=e.document,r=Galleria.IE,s="onhashchange"in e&&(o.mode===undefined||o.mode>7),u,c=function(n){if(u&&!s&&Galleria.IE){n=n||u.location}else{n=a}return parseInt(n.hash.substr(2),10)},f=c(a),l=[],h=function(){n.each(l,function(n,i){i.call(e,c())})},d=function(){n.each(i,function(n,e){e()});t=true},y=function(n){return"/"+n};if(s&&r<8){s=false}if(!s){n(function(){var i=e.setInterval(function(){var n=c();if(!isNaN(n)&&n!=f){f=n;a.hash=y(n);h()}},50);if(r){n('<iframe tabindex="-1" title="empty">').hide().attr("src","about:blank").one("load",function(){u=this.contentWindow;d()}).insertAfter(o.body)}else{d()}})}else{d()}return{change:function(n){l.push(n);if(s){e.onhashchange=h}},set:function(n){if(isNaN(n)){return}if(!s&&r){this.ready(function(){var e=u.document;e.open();e.close();u.location.hash=y(n)})}a.hash=y(n)},ready:function(n){if(!t){i.push(n)}else{n()}}}}()}(jQuery,this);
|
4
themes/galleria/static/js/jquery-1.11.1.min.js
vendored
Normal file
4
themes/galleria/static/js/jquery-1.11.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
145
themes/galleria/templates/index.html
Normal file
145
themes/galleria/templates/index.html
Normal file
|
@ -0,0 +1,145 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
<title>{{ album.title|striptags }}</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="{{ author }}">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Molengo">
|
||||
<link rel="stylesheet" href="{{ theme.url }}/css/galleria.classic.css">
|
||||
<link rel="stylesheet" href="{{ theme.url }}/css/style.min.css">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1><a href="{{ album.index_url }}">{{ index_title }}</a></h1>
|
||||
|
||||
{% if settings.links %}
|
||||
<nav id="menu">
|
||||
<ul>
|
||||
{% for title, link in settings.links %}
|
||||
<li><a href="{{ link }}">{{ title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% if album.breadcrumb %}
|
||||
<h2>
|
||||
{%- for url, title in album.breadcrumb -%}
|
||||
<a href="{{ url }}">{{ title }}</a>
|
||||
{%- if not loop.last %} » {% endif -%}
|
||||
{% endfor -%}
|
||||
</h2>
|
||||
<hr>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
<div id="main" role="main">
|
||||
{% if album.albums %}
|
||||
<div id="albums">
|
||||
<!-- <h1>Albums</h1> -->
|
||||
<ul>
|
||||
{% for alb in album.albums %}
|
||||
<li><a href="{{ alb.url }}">
|
||||
<img src="{{ alb.thumbnail }}" class="album_thumb" alt="{{ alb.name }}" title="{{ alb.name }}" /></a>
|
||||
<span class="album_title">{{ alb.title }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if album.medias %}
|
||||
{% macro img_description(media) -%}
|
||||
{%- if media.big %}<a href='{{ media.big }}'>Full size</a>{% endif %}
|
||||
{% if media.description %}<br>{{ media.description }}{% endif %}
|
||||
|
||||
{%- if media.exif %}
|
||||
<br>
|
||||
{% if media.exif.iso %}ISO: {{ media.exif.iso }}, {% endif %}
|
||||
{% if media.exif.focal %}Focal: {{ media.exif.focal }}, {% endif %}
|
||||
{% if media.exif.exposure %}Exposure: {{ media.exif.exposure }}, {% endif %}
|
||||
{% if media.exif.fstop %}Fstop: {{ media.exif.fstop }}{% endif %}
|
||||
{% if media.exif.datetime %}
|
||||
<br>Date: {{ media.exif.datetime }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
<div id="gallery">
|
||||
{% for media in album.medias %}
|
||||
{% if media.type == "image" %}
|
||||
<a href="{{ media.filename }}">
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
data-title="{{ media.title if media.title else media.filename }}"
|
||||
data-description="{{ img_description(media) }}"/>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if media.type == "video" %}
|
||||
<a href="{{ theme.url }}/img/empty.png">
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
data-title="{{ media.title if media.title else media.filename }}"
|
||||
data-description="{{ img_description(media) }}"
|
||||
data-layer="<video controls>
|
||||
<source src='{{ media.filename }}' type='video/webm' />
|
||||
</video>" />
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if album.zip %}
|
||||
<div id="additionnal-infos" class="row">
|
||||
<p>
|
||||
<a href="{{ album.zip }}"
|
||||
title="Download a zip archive with all images">Download ZIP</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if album.description %}
|
||||
<div id="description">
|
||||
{{ album.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>{% if author %}© {{ author }} - {% endif %}
|
||||
Generated by <a href="{{ sigal_link }}">sigal</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{% if album.medias %}
|
||||
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
|
||||
<script>!window.jQuery && document.write(unescape('%3Cscript src="{{ theme.url }}/js/jquery-1.11.1.min.js"%3E%3C/script%3E'))</script>
|
||||
<script src="{{ theme.url }}/js/galleria-1.3.5.min.js"></script>
|
||||
<script src="{{ theme.url }}/js/galleria.classic.min.js"></script>
|
||||
<script src="{{ theme.url }}/js/galleria.history.min.js"></script>
|
||||
<script>
|
||||
Galleria.configure({
|
||||
imageCrop: false,
|
||||
transition: "fade"
|
||||
});
|
||||
Galleria.run("#gallery");
|
||||
|
||||
Galleria.ready(function() {
|
||||
this.attachKeyboard({
|
||||
right: this.next,
|
||||
left: this.prev
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endif %}
|
||||
{% include 'analytics.html' %}
|
||||
</body>
|
||||
</html>
|
75
utils.py
Normal file
75
utils.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2011-2014 - Simon Conseil
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import shutil
|
||||
from markdown import Markdown
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from . import compat
|
||||
|
||||
#### TODO: split up/delete these functions.
|
||||
|
||||
# TODO: delete
|
||||
def copy(src, dst, symlink=False):
|
||||
"""Copy or symlink the file."""
|
||||
func = os.symlink if symlink else shutil.copy2
|
||||
if symlink and os.path.lexists(dst):
|
||||
os.remove(dst)
|
||||
func(src, dst)
|
||||
|
||||
|
||||
def url_from_path(path):
|
||||
"""Transform path to url, converting backslashes to slashes if needed."""
|
||||
|
||||
if os.sep == '/':
|
||||
return path
|
||||
else:
|
||||
return '/'.join(path.split(os.sep))
|
||||
|
||||
# TODO: move to album.py
|
||||
def read_markdown(filename):
|
||||
# Use utf-8-sig codec to remove BOM if it is present
|
||||
with codecs.open(filename, 'r', 'utf-8-sig') as f:
|
||||
text = f.read()
|
||||
|
||||
md = Markdown(extensions=['meta'])
|
||||
html = md.convert(text)
|
||||
|
||||
return {
|
||||
'title': md.Meta.get('title', [''])[0],
|
||||
'description': html,
|
||||
'meta': md.Meta.copy()
|
||||
}
|
||||
|
||||
# TODO: delete
|
||||
def call_subprocess(cmd):
|
||||
"""Wrapper to call ``subprocess.Popen`` and return stdout & stderr."""
|
||||
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
if not compat.PY2:
|
||||
stderr = stderr.decode('utf8')
|
||||
stdout = stdout.decode('utf8')
|
||||
return p.returncode, stdout, stderr
|
||||
|
178
video.py
Normal file
178
video.py
Normal file
|
@ -0,0 +1,178 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# Copyright (c) 2013 - Christophe-Marie Duquesne
|
||||
# Copyright (c) 2013-2014 - Simon Conseil
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from os.path import splitext
|
||||
|
||||
from . import image
|
||||
from .album import Video
|
||||
from .utils import call_subprocess
|
||||
|
||||
# TODO: merge with image.py
|
||||
|
||||
def check_subprocess(cmd, source, outname):
|
||||
"""Run the command to resize the video and remove the output file if the
|
||||
processing fails.
|
||||
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
returncode, stdout, stderr = call_subprocess(cmd)
|
||||
except KeyboardInterrupt:
|
||||
logger.debug('Process terminated, removing file %s', outname)
|
||||
os.remove(outname)
|
||||
raise
|
||||
|
||||
if returncode:
|
||||
logger.error('Failed to process ' + source)
|
||||
logger.debug('STDOUT:\n %s', stdout)
|
||||
logger.debug('STDERR:\n %s', stderr)
|
||||
logger.debug('Process failed, removing file %s', outname)
|
||||
os.remove(outname)
|
||||
|
||||
|
||||
def video_size(source):
|
||||
"""Returns the dimensions of the video."""
|
||||
|
||||
ret, stdout, stderr = call_subprocess(['ffmpeg', '-i', source])
|
||||
pattern = re.compile(r'Stream.*Video.* ([0-9]+)x([0-9]+)')
|
||||
match = pattern.search(stderr)
|
||||
|
||||
if match:
|
||||
x, y = int(match.groups()[0]), int(match.groups()[1])
|
||||
else:
|
||||
x = y = 0
|
||||
return x, y
|
||||
|
||||
|
||||
def generate_video(source, outname, size, options=None):
|
||||
"""Video processor.
|
||||
|
||||
:param source: path to a video
|
||||
:param outname: path to the generated video
|
||||
:param size: size of the resized video `(width, height)`
|
||||
:param options: array of options passed to ffmpeg
|
||||
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Don't transcode if source is in the required format and
|
||||
# has fitting datedimensions, copy instead.
|
||||
w_src, h_src = video_size(source)
|
||||
w_dst, h_dst = size
|
||||
logger.debug('Video size: %i, %i -> %i, %i', w_src, h_src, w_dst, h_dst)
|
||||
|
||||
base, src_ext = splitext(source)
|
||||
base, dst_ext = splitext(outname)
|
||||
if dst_ext == src_ext and w_src <= w_dst and h_src <= h_dst:
|
||||
logger.debug('Video is smaller than the max size, copying it instead')
|
||||
shutil.copy(source, outname)
|
||||
return
|
||||
|
||||
# http://stackoverflow.com/questions/8218363/maintaining-ffmpeg-aspect-ratio
|
||||
# + I made a drawing on paper to figure this out
|
||||
if h_dst * w_src < h_src * w_dst:
|
||||
# biggest fitting dimension is height
|
||||
resize_opt = ['-vf', "scale=trunc(oh*a/2)*2:%i" % h_dst]
|
||||
else:
|
||||
# biggest fitting dimension is width
|
||||
resize_opt = ['-vf', "scale=%i:trunc(ow/a/2)*2" % w_dst]
|
||||
|
||||
# do not resize if input dimensions are smaller than output dimensions
|
||||
if w_src <= w_dst and h_src <= h_dst:
|
||||
resize_opt = []
|
||||
|
||||
# Encoding options improved, thanks to
|
||||
# http://ffmpeg.org/trac/ffmpeg/wiki/vpxEncodingGuide
|
||||
cmd = ['ffmpeg', '-i', source, '-y'] # -y to overwrite output files
|
||||
if options is not None:
|
||||
cmd += options
|
||||
cmd += resize_opt + [outname]
|
||||
|
||||
logger.debug('Processing video: %s', ' '.join(cmd))
|
||||
check_subprocess(cmd, source, outname)
|
||||
|
||||
|
||||
def generate_thumbnail(source, outname, box, fit=True, options=None):
|
||||
"""Create a thumbnail image for the video source, based on ffmpeg."""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
tmpfile = outname + ".tmp.jpg"
|
||||
|
||||
# dump an image of the video
|
||||
cmd = ['ffmpeg', '-i', source, '-an', '-r', '1',
|
||||
'-vframes', '1', '-y', tmpfile]
|
||||
logger.debug('Create thumbnail for video: %s', ' '.join(cmd))
|
||||
check_subprocess(cmd, source, outname)
|
||||
|
||||
# use the generate_thumbnail function from sigal.image
|
||||
image.generate_thumbnail(tmpfile, outname, box, fit, options)
|
||||
# remove the image
|
||||
os.unlink(tmpfile)
|
||||
|
||||
|
||||
def process_video(filepath, outpath, settings):
|
||||
"""Process a video: resize, create thumbnail."""
|
||||
|
||||
filename = os.path.split(filepath)[1]
|
||||
basename = splitext(filename)[0]
|
||||
outname = os.path.join(outpath, basename + '.webm')
|
||||
|
||||
generate_video(filepath, outname, settings['video_size'],
|
||||
options=settings['webm_options'])
|
||||
|
||||
if settings['make_thumbs']:
|
||||
thumb_name = os.path.join(outpath, get_thumb(settings, filename))
|
||||
generate_thumbnail(
|
||||
outname, thumb_name, settings['thumb_size'],
|
||||
fit=settings['thumb_fit'], options=settings['jpg_options'])
|
||||
|
||||
def get_thumb(settings, filename):
|
||||
"""Return the path to the thumb.
|
||||
|
||||
examples:
|
||||
>>> default_settings = create_settings()
|
||||
>>> get_thumb(default_settings, "bar/foo.jpg")
|
||||
"bar/thumbnails/foo.jpg"
|
||||
>>> get_thumb(default_settings, "bar/foo.png")
|
||||
"bar/thumbnails/foo.png"
|
||||
|
||||
for videos, it returns a jpg file:
|
||||
>>> get_thumb(default_settings, "bar/foo.webm")
|
||||
"bar/thumbnails/foo.jpg"
|
||||
"""
|
||||
|
||||
path, filen = os.path.split(filename)
|
||||
name, ext = os.path.splitext(filen)
|
||||
|
||||
if ext.lower() in Video.extensions:
|
||||
ext = '.jpg'
|
||||
return os.path.join(path, settings['thumb_dir'], settings['thumb_prefix'] +
|
||||
name + settings['thumb_suffix'] + ext)
|
||||
|
||||
|
118
writer.py
Normal file
118
writer.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# Copyright (c) 2009-2014 - Simon Conseil
|
||||
# Copyright (c) 2013 - Christophe-Marie Duquesne
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# 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.
|
||||
|
||||
# Copyright (c) 2014 - Scott Boone
|
||||
# This is a copy of Sigal's Writer module with minor changes to the names of
|
||||
# settings keys for siglican.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import codecs
|
||||
import jinja2
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from distutils.dir_util import copy_tree
|
||||
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, PrefixLoader
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
|
||||
from .pkgmeta import __url__ as sigal_link
|
||||
from .utils import url_from_path
|
||||
|
||||
class Writer(object):
|
||||
"""Generate html pages for each directory of images."""
|
||||
|
||||
def __init__(self, settings, theme=None, index_title=''):
|
||||
self.settings = settings
|
||||
self.output_dir = settings['SIGAL_DESTINATION']
|
||||
self.index_title = index_title
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# check for a custom theme in ./sigal/themes, if not found, look for a
|
||||
# default in the sigal_theme/themes plugin directory
|
||||
self.theme = settings['SIGAL_THEME']
|
||||
default_themes = os.path.normpath(os.path.join(
|
||||
os.path.abspath(os.path.dirname(__file__)), 'themes'))
|
||||
self.logger.debug("siglican: custom theme: %s", self.theme)
|
||||
self.logger.debug("siglican: default themedir: %s", default_themes)
|
||||
if not os.path.exists(self.theme):
|
||||
self.theme = os.path.join(default_themes, os.path.basename(self.theme))
|
||||
if not os.path.exists(self.theme):
|
||||
raise Exception("siglican: unable to find theme: %s" %
|
||||
os.path.basename(self.theme))
|
||||
|
||||
self.logger.info("siglican theme: %s", self.theme)
|
||||
|
||||
# pelican theme path merged with siglican theme path
|
||||
theme_paths = [ os.path.join(self.theme, 'templates'),
|
||||
os.path.join(self.settings['THEME'], 'templates') ]
|
||||
|
||||
# setup jinja env
|
||||
env_options = {'trim_blocks': True}
|
||||
try:
|
||||
if tuple(int(x) for x in jinja2.__version__.split('.')) >= (2, 7):
|
||||
env_options['lstrip_blocks'] = True
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
### note: removed the default loader since sigal default templates
|
||||
### were only used for google analytics, which have been
|
||||
### removed from the siglican plugin port
|
||||
env = Environment(loader=FileSystemLoader(theme_paths),
|
||||
**env_options)
|
||||
|
||||
try:
|
||||
self.template = env.get_template('album.html')
|
||||
except TemplateNotFound:
|
||||
self.logger.error('siglican: template album.html not found')
|
||||
sys.exit(1)
|
||||
|
||||
# Copy the theme files in the output dir
|
||||
self.theme_path = os.path.join(self.output_dir, 'static')
|
||||
copy_tree(os.path.join(self.theme, 'static'), self.theme_path)
|
||||
|
||||
def generate_context(self, album):
|
||||
"""Generate the context dict for the given path."""
|
||||
|
||||
albumdict = {
|
||||
'SIGAL_ALBUM': album,
|
||||
'SIGAL_INDEX_TITLE': self.index_title,
|
||||
'SIGAL_LINK': sigal_link,
|
||||
'SIGAL_THEME_NAME': os.path.basename(self.theme),
|
||||
'SIGAL_THEME_URL': url_from_path(
|
||||
os.path.relpath(self.theme_path,
|
||||
album.dst_path))
|
||||
}
|
||||
albumdict.update(self.settings)
|
||||
return albumdict
|
||||
|
||||
def write(self, album):
|
||||
"""Generate the HTML page and save it."""
|
||||
|
||||
page = self.template.render(**self.generate_context(album))
|
||||
output_file = os.path.join(album.dst_path, album.output_file)
|
||||
self.logger.debug("siglican: write output_file: %s",output_file)
|
||||
|
||||
with codecs.open(output_file, 'w', 'utf-8') as f:
|
||||
f.write(page)
|
Loading…
Reference in a new issue