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