Browse Source

Merge branch 'master' into stream-plugin

pull/304/head
anarcat 8 years ago committed by GitHub
parent
commit
18861679c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .travis.yml
  2. 2
      docs/changelog.rst
  3. 2
      requirements.txt
  4. 17
      setup.py
  5. 13
      sigal/__init__.py
  6. 60
      sigal/compat.py
  7. 29
      sigal/gallery.py
  8. 44
      sigal/image.py
  9. 6
      sigal/plugins/feeds.py
  10. 3
      sigal/plugins/media_page.py
  11. 3
      sigal/plugins/nomedia.py
  12. 12
      sigal/settings.py
  13. 3
      sigal/themes/galleria/static/css/style.css
  14. BIN
      sigal/themes/galleria/static/leaflet/images/layers-2x.png
  15. BIN
      sigal/themes/galleria/static/leaflet/images/layers.png
  16. BIN
      sigal/themes/galleria/static/leaflet/images/marker-icon-2x.png
  17. BIN
      sigal/themes/galleria/static/leaflet/images/marker-icon.png
  18. BIN
      sigal/themes/galleria/static/leaflet/images/marker-shadow.png
  19. 118
      sigal/themes/galleria/static/leaflet/leaflet-providers.js
  20. 13802
      sigal/themes/galleria/static/leaflet/leaflet-src.js
  21. 244
      sigal/themes/galleria/static/leaflet/leaflet.css
  22. 14
      sigal/themes/galleria/static/leaflet/leaflet.js
  23. 17
      sigal/utils.py
  24. 17
      sigal/video.py
  25. 5
      sigal/writer.py
  26. 5
      tests/test_cli.py
  27. 10
      tests/test_utils.py
  28. 2
      tests/test_video.py
  29. 9
      tox.ini

5
.travis.yml

@ -8,11 +8,8 @@ env:
matrix:
include:
- python: 2.7
- python: 3.4
env: PILLOW="Pillow==2.9.0"
- python: 3.5
env: PILLOW="Pillow==3.0.0"
env: PILLOW="Pillow==4.0.0"
- python: 3.6

2
docs/changelog.rst

@ -7,6 +7,8 @@ Version 2.0.dev
Released on 2018-xx-xx.
Sigal now requires Python 3.5+.
Version 1.4.0
~~~~~~~~~~~~~

2
requirements.txt

@ -1,6 +1,7 @@
alabaster
blinker
boto
brotli
click
coverage
feedgenerator
@ -13,3 +14,4 @@ pytest-cov
Sphinx
sphinx-issues
twine
zopfli

17
setup.py

@ -1,14 +1,15 @@
# -*- coding:utf-8 -*-
import os
import sys
from setuptools import setup, find_packages
if sys.version_info[:2] < (3, 5):
sys.exit('Sigal supports Python 3.5+ only')
with open('README.rst') as f:
README = f.read()
with open('docs/changelog.rst') as f:
CHANGELOG = f.read()
# Load package meta from the pkgmeta module without loading the package.
pkgmeta = {}
pkgmeta_file = os.path.join(os.path.dirname(__file__), 'sigal', 'pkgmeta.py')
@ -24,14 +25,15 @@ setup(
author=pkgmeta['__author__'],
author_email='contact@saimon.org',
description='Simple static gallery generator',
long_description=README + '\n' + CHANGELOG,
long_description=README,
packages=find_packages(exclude=['tests*']),
zip_safe=False,
include_package_data=True,
platforms='any',
python_requires='>=3.5',
keywords=['gallery', 'static', 'generator', 'image', 'video', 'galleria'],
install_requires=['blinker', 'click', 'Jinja2', 'Markdown', 'Pillow',
'pilkit'],
install_requires=['blinker', 'click', 'Jinja2', 'Markdown',
'Pillow>=4.0.0', 'pilkit'],
test_requires=['pytest'],
extras_require={
'S3': ['boto']
@ -44,10 +46,7 @@ setup(
'Environment :: Console',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Internet :: WWW/HTTP',

13
sigal/__init__.py

@ -20,20 +20,18 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from __future__ import absolute_import, print_function
import click
import importlib
import io
import locale
import logging
import os
import socketserver
import sys
import time
from click import argument, option
from http import server
from .compat import server, socketserver, string_types
from .gallery import Gallery
from .log import init_logging
from .pkgmeta import __version__
@ -42,6 +40,9 @@ from .utils import copy
_DEFAULT_CONFIG_FILE = 'sigal.conf.py'
if sys.version_info[:2] < (3, 5):
raise Exception('Sigal supports Python 3.5+ only')
@click.group()
@click.version_option(version=__version__)
@ -68,7 +69,7 @@ def init(path):
from pkg_resources import resource_string
conf = resource_string(__name__, 'templates/sigal.conf.py')
with io.open(path, 'w', encoding='utf-8') as f:
with open(path, 'w', encoding='utf-8') as f:
f.write(conf.decode('utf8'))
print("Sample config file created: {}".format(path))
@ -173,7 +174,7 @@ def init_plugins(settings):
for plugin in settings['plugins']:
try:
if isinstance(plugin, string_types):
if isinstance(plugin, str):
mod = importlib.import_module(plugin)
mod.register(settings)
else:

60
sigal/compat.py

@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2013-2018 - 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 locale
import sys
PY2 = sys.version_info[0] == 2
if not PY2:
text_type = str
string_types = (str,)
unichr = chr
strxfrm = locale.strxfrm
from http import server
from urllib.parse import quote as url_quote
import socketserver
import pickle
else: # pragma: no cover
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
import SimpleHTTPServer as server # NOQA
import SocketServer as socketserver # NOQA
try:
import cPickle as pickle
except:
import pickle
class UnicodeMixin(object):
if not PY2:
__str__ = lambda x: x.__unicode__()
else: # pragma: no cover
__str__ = lambda x: unicode(x).encode('utf-8')

29
sigal/gallery.py

@ -25,13 +25,13 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from __future__ import absolute_import, print_function
from copy import copy as shallowcopy
import fnmatch
import locale
import logging
import multiprocessing
import os
import pickle
import random
import sys
import zipfile
@ -41,9 +41,9 @@ from collections import defaultdict
from datetime import datetime
from itertools import cycle
from os.path import isfile, join, splitext
from urllib.parse import quote as url_quote
from . import image, video, signals
from .compat import PY2, UnicodeMixin, strxfrm, url_quote, text_type, pickle
from .image import (process_image, get_exif_tags, get_exif_data, get_size,
get_iptc_data)
from .settings import get_thumb
@ -54,7 +54,7 @@ from .video import process_video
from .writer import Writer
class Media(UnicodeMixin):
class Media:
"""Base Class for media files.
Attributes:
@ -90,7 +90,7 @@ class Media(UnicodeMixin):
def __repr__(self):
return "<%s>(%r)" % (self.__class__.__name__, str(self))
def __unicode__(self):
def __str__(self):
return join(self.path, self.filename)
@property
@ -223,7 +223,7 @@ class Video(Media):
self.mime = get_mime(ext)
class Album(UnicodeMixin):
class Album:
"""Gather all informations on an album.
Attributes:
@ -290,7 +290,7 @@ class Album(UnicodeMixin):
return "<%s>(path=%r, title=%r)" % (self.__class__.__name__, self.path,
self.title)
def __unicode__(self):
def __str__(self):
return (u"{} : ".format(self.path) +
', '.join("{} {}s".format(count, _type)
for _type, count in self.medias_count.items()))
@ -342,13 +342,13 @@ class Album(UnicodeMixin):
root_path = self.path if self.path != '.' else ''
if albums_sort_attr.startswith("meta."):
meta_key = albums_sort_attr.split(".", 1)[1]
key = lambda s: strxfrm(
key = lambda s: locale.strxfrm(
self.gallery.albums[join(root_path, s)].meta.get(meta_key, [''])[0])
else:
key = lambda s: strxfrm(getattr(
key = lambda s: locale.strxfrm(getattr(
self.gallery.albums[join(root_path, s)], albums_sort_attr))
else:
key = strxfrm
key = locale.strxfrm
self.subdirs.sort(key=key,
reverse=self.settings['albums_sort_reverse'])
@ -361,9 +361,9 @@ class Album(UnicodeMixin):
key = lambda s: s.date or datetime.now()
elif medias_sort_attr.startswith('meta.'):
meta_key = medias_sort_attr.split(".", 1)[1]
key = lambda s: strxfrm(s.meta.get(meta_key, [''])[0])
key = lambda s: locale.strxfrm(s.meta.get(meta_key, [''])[0])
else:
key = lambda s: strxfrm(getattr(s, medias_sort_attr))
key = lambda s: locale.strxfrm(getattr(s, medias_sort_attr))
self.medias.sort(key=key,
reverse=self.settings['medias_sort_reverse'])
@ -675,10 +675,7 @@ class Gallery(object):
# 63 is the total length of progressbar, label, percentage, etc
available_length = get_terminal_size()[0] - 64
if x and available_length > 10:
text = text_type(x.name)[:available_length]
if PY2:
text = text.encode('utf-8')
return text
return x.name[:available_length]
else:
return ""

44
sigal/image.py

@ -33,7 +33,6 @@
import logging
import os
import PIL
import pilkit.processors
import sys
import warnings
@ -42,13 +41,11 @@ import fractions
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 PIL import IptcImagePlugin
from PIL import Image as PILImage, ImageOps, IptcImagePlugin
from pilkit.processors import Transpose
from pilkit.utils import save_image
from . import compat, signals, utils
from . import signals, utils
from .settings import get_thumb, Status
@ -179,9 +176,10 @@ def process_image(filepath, outpath, settings):
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=options,
thumb_fit_centering=settings["thumb_fit_centering"])
generate_thumbnail(
outname, thumb_name, settings['thumb_size'],
fit=settings['thumb_fit'], options=options,
thumb_fit_centering=settings["thumb_fit_centering"])
except Exception as e:
logger.info('Failed to process: %r', e)
if logger.getEffectiveLevel() == logging.DEBUG:
@ -212,10 +210,6 @@ def get_exif_data(filename):
logger = logging.getLogger(__name__)
if PIL.PILLOW_VERSION == '3.0.0':
warnings.warn('Pillow 3.0.0 is broken with EXIF data, consider using '
'another version if you want to use EXIF data.')
img = _read_image(filename)
try:
@ -282,9 +276,6 @@ def get_exif_tags(data, datetime_format='%c'):
fnumber = data['FNumber']
try:
simple['fstop'] = float(fnumber[0]) / fnumber[1]
except IndexError:
# Pillow == 3.0
simple['fstop'] = float(fnumber[0])
except Exception:
logger.debug('Skipped invalid FNumber: %r', fnumber, exc_info=True)
@ -292,9 +283,6 @@ def get_exif_tags(data, datetime_format='%c'):
focal = data['FocalLength']
try:
simple['focal'] = round(float(focal[0]) / focal[1])
except IndexError:
# Pillow == 3.0
simple['focal'] = round(focal[0])
except Exception:
logger.debug('Skipped invalid FocalLength: %r', focal,
exc_info=True)
@ -305,9 +293,6 @@ def get_exif_tags(data, datetime_format='%c'):
try:
simple['exposure'] = str(fractions.Fraction(exptime[0],
exptime[1]))
except IndexError:
# Pillow == 3.0
simple['exposure'] = exptime[0]
except ZeroDivisionError:
logger.info('Invalid ExposureTime: %r', exptime)
elif isinstance(exptime, int):
@ -316,24 +301,15 @@ def get_exif_tags(data, datetime_format='%c'):
logger.info('Unknown format for ExposureTime: %r', exptime)
if data.get('ISOSpeedRatings'):
if isinstance(data['ISOSpeedRatings'], tuple):
# Pillow == 3.0
simple['iso'] = data['ISOSpeedRatings'][0]
else:
simple['iso'] = data['ISOSpeedRatings']
simple['iso'] = data['ISOSpeedRatings']
if 'DateTimeOriginal' in data:
try:
# Remove null bytes at the end if necessary
date = data['DateTimeOriginal'].rsplit('\x00')[0]
except AttributeError:
# Pillow == 3.0
date = data['DateTimeOriginal'][0]
# Remove null bytes at the end if necessary
date = data['DateTimeOriginal'].rsplit('\x00')[0]
try:
simple['dateobj'] = datetime.strptime(date, '%Y:%m:%d %H:%M:%S')
dt = simple['dateobj'].strftime(datetime_format)
simple['datetime'] = dt.decode('utf8') if compat.PY2 else dt
simple['datetime'] = simple['dateobj'].strftime(datetime_format)
except (ValueError, TypeError) as e:
logger.info(u'Could not parse DateTimeOriginal: %s', e)

6
sigal/plugins/feeds.py

@ -18,14 +18,13 @@ Example::
"""
import codecs
import logging
import os
from datetime import datetime
from feedgenerator import Atom1Feed, Rss201rev2Feed
from jinja2 import Markup
from sigal import signals, compat
from sigal import signals
logger = logging.getLogger(__name__)
@ -72,8 +71,7 @@ def generate_feed(gallery, medias, feed_type=None, feed_url='', nb_items=0):
output_file = os.path.join(root_album.dst_path, feed_url.split('/')[-1])
logger.info('Generate %s feeds: %s', feed_type.upper(), output_file)
encoding = 'utf-8' if not compat.PY2 else None
with codecs.open(output_file, 'w', encoding) as f:
with open(output_file, 'w', encoding='utf8') as f:
feed.write(f, 'utf-8')

3
sigal/plugins/media_page.py

@ -31,7 +31,6 @@ previous/next :class:`~sigal.gallery.Media` objects.
"""
import codecs
import os
from sigal import signals
@ -65,7 +64,7 @@ class PageWriter(Writer):
output_file = "%s.html" % file_path
with codecs.open(output_file, 'w', 'utf-8') as f:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(page)

3
sigal/plugins/nomedia.py

@ -48,7 +48,6 @@ them for good.
"""
import io
import logging
import os
from sigal import signals
@ -107,7 +106,7 @@ def filter_nomedia(album, settings=None):
album.medias = []
else:
with io.open(nomediapath, "r") as nomediaFile:
with open(nomediapath, "r") as nomediaFile:
logger.info("Found a .nomedia file in %s, ignoring its "
"entries", album.name)
ignored = nomediaFile.read().split("\n")

12
sigal/settings.py

@ -22,14 +22,11 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import locale
import logging
import os
from os.path import abspath, isabs, join, normpath
from pprint import pformat
from .compat import PY2, text_type
_DEFAULT_CONFIG = {
'albums_sort_attr': 'name',
@ -146,21 +143,12 @@ def read_settings(filename=None):
'templates')):
paths.append('theme')
enc = locale.getpreferredencoding() if PY2 else None
for p in paths:
# paths must to be unicode strings so that os.walk will return
# unicode dirnames and filenames
if PY2 and isinstance(settings[p], str):
settings[p] = settings[p].decode(enc)
path = settings[p]
if path and not isabs(path):
settings[p] = abspath(normpath(join(settings_path, path)))
logger.debug("Rewrite %s : %s -> %s", p, path, settings[p])
if settings['title'] and not isinstance(settings['title'], text_type):
settings['title'] = settings['title'].decode('utf8')
for key in ('img_size', 'thumb_size', 'video_size'):
w, h = settings[key]
if h > w:

3
sigal/themes/galleria/static/css/style.css

@ -126,6 +126,9 @@ header h1 a:hover, header h2 a:hover {
width: 100%;
margin: 0 auto;
}
.galleria-theme-classic .galleria-info-text {
background-color: rgba(0, 0, 0, 0.7);
}
.icons {
top: 10px;

BIN
sigal/themes/galleria/static/leaflet/images/layers-2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
sigal/themes/galleria/static/leaflet/images/layers.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 696 B

BIN
sigal/themes/galleria/static/leaflet/images/marker-icon-2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
sigal/themes/galleria/static/leaflet/images/marker-icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
sigal/themes/galleria/static/leaflet/images/marker-shadow.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 618 B

118
sigal/themes/galleria/static/leaflet/leaflet-providers.js

@ -50,11 +50,6 @@
};
}
var forceHTTP = window.location.protocol === 'file:' || provider.options.forceHTTP;
if (provider.url.indexOf('//') === 0 && forceHTTP) {
provider.url = 'http:' + provider.url;
}
// If retina option is set
if (provider.options.retina) {
// Check retina screen
@ -95,7 +90,7 @@
L.TileLayer.Provider.providers = {
OpenStreetMap: {
url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
options: {
maxZoom: 19,
attribution:
@ -110,64 +105,70 @@
}
},
DE: {
url: 'http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png',
url: 'https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png',
options: {
maxZoom: 18
}
},
France: {
url: '//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
url: 'https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
options: {
maxZoom: 20,
attribution: '&copy; Openstreetmap France | {attribution.OpenStreetMap}'
}
},
HOT: {
url: '//{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
url: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
options: {
attribution: '{attribution.OpenStreetMap}, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>'
}
},
BZH: {
url: 'http://tile.openstreetmap.bzh/br/{z}/{x}/{y}.png',
options: {
attribution: '{attribution.OpenStreetMap}, Tiles courtesy of <a href="http://www.openstreetmap.bzh/" target="_blank">Breton OpenStreetMap Team</a>',
bounds: [[46.2, -5.5], [50, 0.7]]
}
}
}
},
OpenSeaMap: {
url: 'http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png',
url: 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png',
options: {
attribution: 'Map data: &copy; <a href="http://www.openseamap.org">OpenSeaMap</a> contributors'
}
},
OpenTopoMap: {
url: '//{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
url: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
options: {
maxZoom: 17,
attribution: 'Map data: {attribution.OpenStreetMap}, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
}
},
Thunderforest: {
url: '//{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png',
url: 'https://{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png?apikey={apikey}',
options: {
attribution:
'&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, {attribution.OpenStreetMap}',
variant: 'cycle'
variant: 'cycle',
apikey: '<insert your api key here>',
maxZoom: 22
},
variants: {
OpenCycleMap: 'cycle',
Transport: {
options: {
variant: 'transport',
maxZoom: 19
variant: 'transport'
}
},
TransportDark: {
options: {
variant: 'transport-dark',
maxZoom: 19
variant: 'transport-dark'
}
},
SpinalMap: {
options: {
variant: 'spinal-map',
maxZoom: 11
variant: 'spinal-map'
}
},
Landscape: 'landscape',
@ -176,7 +177,7 @@
}
},
OpenMapSurfer: {
url: 'http://korona.geog.uni-heidelberg.de/tiles/{variant}/x={x}&y={y}&z={z}',
url: 'https://korona.geog.uni-heidelberg.de/tiles/{variant}/x={x}&y={y}&z={z}',
options: {
maxZoom: 20,
variant: 'roads',
@ -199,8 +200,9 @@
}
},
Hydda: {
url: '//{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png',
url: 'https://{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png',
options: {
maxZoom: 18,
variant: 'full',
attribution: 'Tiles courtesy of <a href="http://openstreetmap.se/" target="_blank">OpenStreetMap Sweden</a> &mdash; Map data {attribution.OpenStreetMap}'
},
@ -211,16 +213,18 @@
}
},
MapBox: {
url: '//api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',
url: 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',
options: {
attribution:
'Imagery from <a href="http://mapbox.com/about/maps/">MapBox</a> &mdash; ' +
'Map data {attribution.OpenStreetMap}',
subdomains: 'abcd'
subdomains: 'abcd',
id: 'streets',
accessToken: '<insert your access token here>',
}
},
Stamen: {
url: '//stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}',
url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}',
options: {
attribution:
'Map tiles by <a href="http://stamen.com">Stamen Design</a>, ' +
@ -277,7 +281,7 @@
}
},
Esri: {
url: '//server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}',
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}',
options: {
variant: 'World_Street_Map',
attribution: 'Tiles &copy; Esri'
@ -361,10 +365,11 @@
}
},
OpenWeatherMap: {
url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png',
url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png?appid={apiKey}',
options: {
maxZoom: 19,
attribution: 'Map data &copy; <a href="http://openweathermap.org">OpenWeatherMap</a>',
apiKey:'<insert your api key here>',
opacity: 0.5
},
variants: {
@ -392,7 +397,7 @@
* envirionments.
*/
url:
'//{s}.{base}.maps.cit.api.here.com/maptile/2.1/' +
'https://{s}.{base}.maps.cit.api.here.com/maptile/2.1/' +
'{type}/{mapID}/{variant}/{z}/{x}/{y}/{size}/{format}?' +
'app_id={app_id}&app_code={app_code}&lg={language}',
options: {
@ -494,7 +499,7 @@
}
},
CartoDB: {
url: 'http://{s}.basemaps.cartocdn.com/{variant}/{z}/{x}/{y}.png',
url: 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/{variant}/{z}/{x}/{y}.png',
options: {
attribution: '{attribution.OpenStreetMap} &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
subdomains: 'abcd',
@ -528,17 +533,22 @@
}
},
BasemapAT: {
url: '//maps{s}.wien.gv.at/basemap/{variant}/normal/google3857/{z}/{y}/{x}.{format}',
url: 'https://maps{s}.wien.gv.at/basemap/{variant}/normal/google3857/{z}/{y}/{x}.{format}',
options: {
maxZoom: 19,
attribution: 'Datenquelle: <a href="www.basemap.at">basemap.at</a>',
attribution: 'Datenquelle: <a href="https://www.basemap.at">basemap.at</a>',
subdomains: ['', '1', '2', '3', '4'],
format: 'png',
bounds: [[46.358770, 8.782379], [49.037872, 17.189532]],
variant: 'geolandbasemap'
},
variants: {
basemap: 'geolandbasemap',
basemap: {
options: {
maxZoom: 20, // currently only in Vienna
variant: 'geolandbasemap'
}
},
grau: 'bmapgrau',
overlay: 'bmapoverlay',
highdpi: {
@ -549,14 +559,32 @@
},
orthofoto: {
options: {
maxZoom: 20, // currently only in Vienna
variant: 'bmaporthofoto30cm',
format: 'jpeg'
}
}
}
},
nlmaps: {
url: 'https://geodata.nationaalgeoregister.nl/tiles/service/wmts/{variant}/EPSG:3857/{z}/{x}/{y}.png',
options: {
minZoom: 6,
maxZoom: 19,
bounds: [[50.5, 3.25], [54, 7.6]],
attribution: 'Kaartgegevens &copy; <a href="kadaster.nl">Kadaster</a>'
},
variants: {
'standaard': 'brtachtergrondkaart',
'pastel': 'brtachtergrondkaartpastel',
'grijs': 'brtachtergrondkaartgrijs',
'luchtfoto': {
'url': 'https://geodata.nationaalgeoregister.nl/luchtfoto/rgb/wmts/1.0.0/2016_ortho25/EPSG:3857/{z}/{x}/{y}.png',
}
}
},
NASAGIBS: {
url: '//map1.vis.earthdata.nasa.gov/wmts-webmerc/{variant}/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}',
url: 'https://map1.vis.earthdata.nasa.gov/wmts-webmerc/{variant}/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}',
options: {
attribution:
'Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System ' +
@ -620,7 +648,7 @@
// z0-9 - 1:1m
// z10-11 - quarter inch (1:253440)
// z12-18 - one inch (1:63360)
url: '//nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg',
url: 'https://nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg',
options: {
attribution: '<a href="http://geo.nls.uk/maps/">National Library of Scotland Historic Maps</a>',
bounds: [[49.6, -12], [61.7, 3]],
@ -628,6 +656,30 @@
maxZoom: 18,
subdomains: '0123',
}
},
JusticeMap: {
// Justice Map (http://www.justicemap.org/)
// Visualize race and income data for your community, county and country.
// Includes tools for data journalists, bloggers and community activists.
url: 'http://www.justicemap.org/tile/{size}/{variant}/{z}/{x}/{y}.png',
options: {
attribution: '<a href="http://www.justicemap.org/terms.php">Justice Map</a>',
// one of 'county', 'tract', 'block'
size: 'county',
// Bounds for USA, including Alaska and Hawaii
bounds: [[14, -180], [72, -56]]
},
variants: {
income: 'income',
americanIndian: 'indian',
asian: 'asian',
black: 'black',
hispanic: 'hispanic',
multi: 'multi',
nonWhite: 'nonwhite',
white: 'white',
plurality: 'plural'
}
}
};

13802
sigal/themes/galleria/static/leaflet/leaflet-src.js

File diff suppressed because it is too large Load Diff

244
sigal/themes/galleria/static/leaflet/leaflet.css

@ -1,16 +1,12 @@
/* required styles */
.leaflet-map-pane,
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-pane,
.leaflet-tile-container,
.leaflet-overlay-pane,
.leaflet-shadow-pane,
.leaflet-marker-pane,
.leaflet-popup-pane,
.leaflet-overlay-pane svg,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
@ -20,7 +16,6 @@
}
.leaflet-container {
overflow: hidden;
-ms-touch-action: none;
}
.leaflet-tile,
.leaflet-marker-icon,
@ -28,20 +23,53 @@
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
-webkit-user-drag: none;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container img {
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg,
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer {
max-width: none !important;
max-height: none !important;
}
/* stupid Android 2 doesn't understand "max-width: none" properly */
.leaflet-container img.leaflet-image-layer {
max-width: 15000px !important;
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
@ -52,18 +80,26 @@
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-tile-pane { z-index: 2; }
.leaflet-objects-pane { z-index: 3; }
.leaflet-overlay-pane { z-index: 4; }
.leaflet-shadow-pane { z-index: 5; }
.leaflet-marker-pane { z-index: 6; }
.leaflet-popup-pane { z-index: 7; }
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
@ -80,7 +116,8 @@
.leaflet-control {
position: relative;
z-index: 7;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
@ -124,7 +161,9 @@
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile,
.leaflet-fade-anim .leaflet-tile {
will-change: opacity;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
@ -132,11 +171,17 @@
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-tile-loaded,
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
@ -144,8 +189,7 @@
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile,
.leaflet-touching .leaflet-zoom-animated {
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
@ -159,24 +203,44 @@
/* cursors */
.leaflet-clickable {
.leaflet-interactive {
cursor: pointer;
}
.leaflet-container {
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-container,
.leaflet-dragging .leaflet-clickable {
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
@ -249,7 +313,14 @@
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
@ -258,16 +329,10 @@
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-control-zoom-out {
font-size: 20px;
}
.leaflet-touch .leaflet-control-zoom-in {
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
.leaflet-touch .leaflet-control-zoom-out {
font-size: 24px;
}
/* layers control */
@ -303,6 +368,11 @@
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
@ -317,6 +387,11 @@
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path {
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
@ -354,8 +429,8 @@
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: content-box;
box-sizing: content-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
@ -386,6 +461,7 @@
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
@ -400,11 +476,13 @@
margin: 18px 0;
}
.leaflet-popup-tip-container {
margin: 0 auto;
width: 40px;
height: 20px;
position: relative;
position: absolute;
left: 50%;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
@ -422,7 +500,7 @@
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
@ -430,6 +508,7 @@
top: 0;
right: 0;
padding: 4px 4px 0 0;
border: none;
text-align: center;
width: 18px;
height: 14px;
@ -476,3 +555,82 @@
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-clickable {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}

14
sigal/themes/galleria/static/leaflet/leaflet.js

File diff suppressed because one or more lines are too long

17
sigal/utils.py

@ -20,14 +20,10 @@
# 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 markupsafe import Markup
from subprocess import Popen, PIPE
from . import compat
VIDEO_MIMES = {'.mp4': 'video/mp4',
'.webm': 'video/webm',
@ -75,7 +71,7 @@ def read_markdown(filename):
# Use utf-8-sig codec to remove BOM if it is present. This is only possible
# this way prior to feeding the text to the markdown parser (which would
# also default to pure utf-8)
with codecs.open(filename, 'r', 'utf-8-sig') as f:
with open(filename, 'r', encoding='utf-8-sig') as f:
text = f.read()
md = Markdown(extensions=['markdown.extensions.meta',
@ -98,17 +94,6 @@ def read_markdown(filename):
return output
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
def is_valid_html5_video(ext):
"""Checks if ext is a supported HTML5 video."""
return ext in VIDEO_MIMES.keys()

17
sigal/video.py

@ -21,17 +21,16 @@
# 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
import subprocess
from os.path import splitext
from . import image, utils
from .settings import get_thumb, Status
from .utils import call_subprocess, is_valid_html5_video
from .utils import is_valid_html5_video
class SubprocessException(Exception):
@ -45,16 +44,17 @@ def check_subprocess(cmd, source, outname):
"""
logger = logging.getLogger(__name__)
try:
returncode, stdout, stderr = call_subprocess(cmd)
res = subprocess.run(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except KeyboardInterrupt:
logger.debug('Process terminated, removing file %s', outname)
if os.path.isfile(outname):
os.remove(outname)
raise
if returncode:
logger.debug('STDOUT:\n %s', stdout)
logger.debug('STDERR:\n %s', stderr)
if res.returncode:
logger.debug('STDOUT:\n %s', res.stdout.decode('utf8'))
logger.debug('STDERR:\n %s', res.stderr.decode('utf8'))
if os.path.isfile(outname):
logger.debug('Removing file %s', outname)
os.remove(outname)
@ -64,7 +64,8 @@ def check_subprocess(cmd, source, outname):
def video_size(source, converter='ffmpeg'):
"""Returns the dimensions of the video."""
ret, stdout, stderr = call_subprocess([converter, '-i', source])
res = subprocess.run([converter, '-i', source], stderr=subprocess.PIPE)
stderr = res.stderr.decode('utf8')
pattern = re.compile(r'Stream.*Video.* ([0-9]+)x([0-9]+)')
match = pattern.search(stderr)
rot_pattern = re.compile(r'rotate\s*:\s*-?(90|270)')

5
sigal/writer.py

@ -21,9 +21,6 @@
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from __future__ import absolute_import
import codecs
import jinja2
import logging
import imp
@ -121,5 +118,5 @@ class Writer(object):
page = self.template.render(**self.generate_context(album))
output_file = os.path.join(album.dst_path, album.output_file)
with codecs.open(output_file, 'w', 'utf-8') as f:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(page)

5
tests/test_cli.py

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
import io
import logging
import os
from click.testing import CliRunner
@ -52,7 +51,7 @@ def test_build(tmpdir, disconnect_signals):
'-n', 1, '--debug'])
assert result.exit_code == 1
with io.open(config_file) as f:
with open(config_file) as f:
text = f.read()
text += """
@ -71,7 +70,7 @@ rss_feed = {'feed_url': 'http://example.org/feed.rss', 'nb_items': 10}
atom_feed = {'feed_url': 'http://example.org/feed.atom', 'nb_items': 10}
"""
with io.open(config_file, 'w') as f:
with open(config_file, 'w') as f:
f.write(text)
result = runner.invoke(build, ['pictures', 'build',

10
tests/test_utils.py

@ -70,16 +70,6 @@ def test_read_markdown_empty_file(tmpdir):
assert m['description'] == ''
def test_call_subprocess():
returncode, stdout, stderr = utils.call_subprocess(['echo', 'ok'])
assert returncode == 0
assert stdout == 'ok\n'
assert stderr == ''
# returncode, stdout, stderr = utils.call_subprocess(['/usr/bin/false'])
# assert returncode == 1
def test_is_valid_html5_video():
assert utils.is_valid_html5_video('.webm') is True
assert utils.is_valid_html5_video('.mpeg') is False

2
tests/test_video.py

@ -1,7 +1,5 @@
# -*- coding:utf-8 -*-
from __future__ import division
import os
import pytest

9
tox.ini

@ -1,18 +1,15 @@
[tox]
envlist = py{27,33,34,35}-pillow{30,31},report,check
envlist = py{35,36},report,check
skip_missing_interpreters = True
[testenv]
commands = py.test --cov sigal --cov-report term-missing
deps =
pillow30: Pillow==3.0.0
pillow31: Pillow>3.0.0
pytest
pytest-capturelog
pytest-cov
[testenv:report]
basepython = python3.5
basepython = python3.6
commands =
coverage combine
coverage report
@ -20,7 +17,7 @@ usedevelop = true
deps = coverage
[testenv:check]
basepython = python3.5
basepython = python3.6
deps =
docutils
check-manifest

Loading…
Cancel
Save