Initial commit

master
Dustin 2017-03-16 09:33:55 -05:00
commit 0f164913d0
14 changed files with 447 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/build/
/dist/
*.egg-info/
__pycache__/
*.py[co]

2
MANIFEST.in Normal file
View File

@ -0,0 +1,2 @@
graft src/gallerygen/resources
recursive-include src/gallerygen/templates *.j2

30
setup.py Normal file
View File

@ -0,0 +1,30 @@
from setuptools import find_packages, setup
import sys
setup(
name='gallerygen',
version='1',
description='Static photo gallery website generator',
author='Dustin C. Hatch',
author_email='dustin@hatch.name',
url='http://dustin.hatch.name/projects/gallerygen',
license='GPL-3',
package_dir={'': 'src'},
packages=find_packages('src'),
package_data={
'gallerygen': [
'resources/*',
'resources/css/*',
'templates/*.j2',
],
},
install_requires=[
'Jinja2',
],
entry_points={
'console_scripts': [
'gallerygen=gallerygen:main',
],
},
)

271
src/gallerygen/__init__.py Normal file
View File

@ -0,0 +1,271 @@
#!/usr/bin/env python
from PIL import Image
import argparse
import functools
import jinja2
import logging
import os
import pkg_resources
import shutil
import yaml
log = logging.getLogger('gallerygen')
class StaticMeta(type):
def __call__(cls):
return cls
class Gallerygen(metaclass=StaticMeta):
dist = pkg_resources.get_distribution('Gallerygen')
version = dist.version
class Album:
SPEC_SECTION_PREFIX = 'album '
def __init__(self, name, title=None, basedir=None):
self.name = name
if title is None:
title = name
self.title = title
if basedir is None:
basedir = os.getcwd()
self.path = os.path.join(basedir, name)
self.description = ''
self.cover = None
self.captions = {}
@classmethod
def from_spec(cls, spec, captions=None, basedir=None):
self = cls(spec['name'], spec.get('title'))
self.description = spec.get('description', '')
self.cover = spec.get('cover')
if captions:
self.captions.update(captions)
return self
@property
def photos(self):
for filename in sorted(os.listdir(self.path)):
path = os.path.join(self.path, filename)
if os.path.isfile(path):
yield filename
class Gallery:
DEFAULT_LOADER = jinja2.PackageLoader(__name__)
DEFAULT_SPEC = {
'gallery': {
'title': 'Photo Gallery',
},
'albums': [],
'captions': {},
}
def __init__(self, title, basedir=None):
self.title = title
if basedir is None:
basedir = os.getcwd()
self.basedir = basedir
self.templates = None
self.extra_resources = None
self.albums = []
self.cover_size = (300, 300) # TODO: configurable
self.thumbnail_size = (200, 200) # TODO: configurable
@classmethod
def from_spec_fp(cls, fp, basedir=None):
spec = cls.DEFAULT_SPEC.copy()
spec.update(yaml.safe_load(fp))
self = cls(spec['gallery']['title'], basedir)
self.templates = spec['gallery'].get('templates')
self.extra_resources = spec['gallery'].get('extra_resources')
for item in spec['albums']:
captions = spec['captions'].get(item['name'])
self.albums.append(Album.from_spec(item, captions, self.basedir))
return self
def get_template_loader(self):
if self.templates:
tmpl_path = self.templates
if not os.path.isabs(tmpl_path):
tmpl_path = os.path.join(self.basedir, tmpl_path)
return jinja2.ChoiceLoader([
jinja2.FileSystemLoader(tmpl_path),
self.DEFAULT_LOADER,
])
else:
return self.DEFAULT_LOADER
def generate(self, outdir=None):
if not outdir:
outdir = os.getcwd()
mkdir_p(outdir)
resdir = os.path.join(outdir, 'res')
env = jinja2.Environment(loader=self.get_template_loader())
env.globals.update(
gallery=self,
gallerygen=Gallerygen(),
)
self.render_index(outdir, env)
for album in self.albums:
self.render_album(album, outdir, env)
self.copy_resources(resdir)
def render_index(self, outdir, env):
context = {
'page_title': self.title,
'res_path': 'res',
'cover': functools.partial(self.mkcover, outdir),
}
tmpl = env.get_template('index.html.j2')
self.render(os.path.join(outdir, 'index.html'), tmpl, context)
def render_album(self, album, outdir, env):
context = {
'page_title': album.title,
'res_path': '../res',
'album': album,
'thumbnail': functools.partial(self.mkthumbnail, outdir, album),
}
tmpl = env.get_template('album.html.j2')
subdir = os.path.join(outdir, album.name)
mkdir_p(subdir)
self.render(os.path.join(subdir, 'index.html'), tmpl, context)
for photo in album.photos:
self.render_photo(album, photo, subdir, env)
def render_photo(self, album, photo, subdir, env):
context = {
'page_title': photo,
'res_path': '../res',
'album': album,
'photo': photo,
'caption': album.captions.get(photo, ''),
}
tmpl = env.get_template('photo.html.j2')
self.render(os.path.join(subdir, photo + '.html'), tmpl, context)
srcname = os.path.join(self.basedir, album.name, photo)
destname = os.path.join(subdir, photo)
cp_u(srcname, destname)
def render(self, filename, tmpl, context):
log.info('Rendering %s', filename)
with open(filename, 'w') as f:
f.write(tmpl.render(**context))
def copy_resources(self, resdir):
mkdir_p(resdir)
self.copydir(resdir, pkg_resources.resource_filename(
Gallerygen.dist.key,
'resources',
))
extra_resources = self.extra_resources
if extra_resources:
if not os.path.isabs(extra_resources):
extra_resources = os.path.join(self.basedir, extra_resources)
self.copydir(resdir, extra_resources)
def copydir(self, resdir, src):
for dirpath, dirnames, filenames in os.walk(src):
subdir = os.path.relpath(dirpath, src)
for dirname in dirnames:
destname = os.path.join(resdir, subdir, dirname)
mkdir_p(destname)
for filename in filenames:
destname = os.path.normpath(
os.path.join(resdir, subdir, filename)
)
srcname = os.path.join(dirpath, filename)
cp_u(srcname, destname)
def mkcover(self, outdir, album):
cover = album.cover or next(album.photos)
log.info('Creating cover for album %s: %s', album.name, cover)
return self.gen_thumbnail(
outdir,
'covers',
album,
cover,
self.cover_size,
)
def mkthumbnail(self, outdir, album, photo):
log.info('Creating thubnail: %s (in %s)', photo, album.name)
return self.gen_thumbnail(
outdir,
'thumbnails',
album,
photo,
self.thumbnail_size,
)
def gen_thumbnail(self, outdir, prefix, album, photo, size):
subdir = os.path.join(prefix, album.name)
destdir = os.path.join(outdir, subdir)
img_path = os.path.join(self.basedir, album.name, photo)
try:
mkdir_p(destdir)
with open(img_path, 'rb') as f:
im = Image.open(f)
im.thumbnail(size)
im.save(os.path.join(destdir, photo))
except:
log.exception('Failed to make thumbnail image:')
return os.path.join(subdir, photo)
def cp_u(srcname, destname):
if os.path.exists(destname):
s_mtime = os.path.getmtime(srcname)
d_mtime = os.path.getmtime(destname)
if s_mtime <= d_mtime:
return
log.info('Copying %s %s', srcname, destname)
shutil.copy2(srcname, destname)
def mkdir_p(dirname):
dirname = os.path.normpath(dirname)
if not os.path.isdir(dirname):
log.debug('Creating directory: %s', dirname)
os.makedirs(dirname)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
'spec',
type=argparse.FileType('r'),
help='Path to specification file',
)
parser.add_argument(
'output',
nargs='?',
help='Path to output directory',
)
return parser.parse_args()
def main():
args = parse_args()
logging.basicConfig(level=logging.DEBUG)
with args.spec:
basedir = os.path.dirname(args.spec.name)
gallery = Gallery.from_spec_fp(args.spec, basedir)
gallery.generate(args.output)
if __name__ == '__main__':
main()

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
.photo figcaption {
margin: .5em .25em;
font-size: larger;
}
footer .attribution {
margin-top: 2em;
font-size: smaller;
}

View File

@ -0,0 +1 @@
{% extends "default-album.html.j2" %}

View File

@ -0,0 +1 @@
{% extends "default-base.html.j2" %}

View File

@ -0,0 +1,26 @@
{% extends "base.html.j2" %}
{%- block title %}
<title>{{ album.title }} :: {{ gallery.title }}</title>
{%- endblock title %}
{%- block content %}
<main class="album">
<section>
<div class="row columns">
<p class="album-description">{{ album.description }}</p>
</div>
</section>
<section class="thumbnails">
<div class="row columns">
{%- for photo in album.photos %}
<a href="{{ photo }}.html"><img src="../{{ thumbnail(photo) }}"
{%- if album.captions[photo] %} title="{{ album.captions[photo] }}"{% endif %}
class="thumbnail"></a>
{%- endfor %}
</div>
</section>
</main>
{%- endblock content %}

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html class="no-js" lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
{%- block head %}
{%- block title %}
<title>{{ page_title }}</title>
{%- endblock title %}
{%- block meta %}
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{%- endblock meta %}
{%- block head_links %}
<link href="{{ res_path }}/css/foundation.min.css" rel="stylesheet" />
<link href="{{ res_path }}/css/gallerygen.css" rel="stylesheet" />
{%- endblock head_links %}
{%- endblock head %}
</head>
<body>
{%- block body %}
{%- block header %}
<header class="row column">
<h1 class="title">{{ page_title }}</h1>
</header>
{%- endblock header %}
{%- block content %}
{%- endblock content %}
{%- block footer %}
<footer class="row column">
<div class="attribution">
Created with Gallerygen {{ gallerygen.version }}
</div>
</footer>
{%- endblock footer %}
{%- block script %}
{%- endblock script %}
{%- endblock body %}
</body>
</html>

View File

@ -0,0 +1,38 @@
{% extends "base.html.j2" %}
{%- macro album_section(album) %}
<div class="album large-6 columns">
<div class="row">
<div class="large-6 columns">
<a href="{{ album.name }}/index.html"><img class="thumbnail"
src="{{ cover(album) }}" />
</a>
</div>
<div class="large-6 columns">
<h3><a href="{{ album.name }}/index.html">{{ album.title }}</a></h3>
<p class="album-description">
{{ album.description }}
</p>
</div>
</div>
</div>
{%- endmacro %}
{%- block content %}
<main class="index">
{%- block albumlist %}
<div class="row columns">
<section class="album-list">
<h2>Photo Albums</h2>
<div class="row">
{%- for album in gallery.albums %}
{{ album_section(album) }}
{%- endfor %}
</div>
</section>
</div>
{%- endblock %}
</main>
{%- endblock content %}

View File

@ -0,0 +1,18 @@
{% extends "base.html.j2" %}
{%- block title %}
<title>{{ photo }} :: {{ album.title }} :: {{ gallery.title }}</title>
{%- endblock title %}
{%- block content %}
<main class="photo">
<section class="photo">
<div class="row columns">
<a href="{{ photo }}"><img src="{{ photo }}" style="width: 100%;" /></a>
{%- if caption %}
<figcaption>{{ caption }}</figcaption>
{%- endif %}
</div>
</section>
</main>
{%- endblock content %}

View File

@ -0,0 +1 @@
{% extends "default-index.html.j2" %}

View File

@ -0,0 +1 @@
{% extends "default-photo.html.j2" %}