#!/usr/bin/env python
# My contribution to the world not needing *another* web gallery tool.
# Needs ImageMagick.
import sys, os, stat, getopt, fnmatch
import cPickle as pickle
recognised_extensions = [ "jpeg", "jpg", "gif", "png", "tif", "tiff" ]
thumbs_dir = "g"
thumb_width = 160
thumb_height = 160
thumbs_per_page = 500
template_file = None
ignore_patterns = []
default_template = \
"""
__TITLE__
__TITLE__
__BLURB__
__BODY__
"""
def capture(cmd):
"""Run a command, returning the tuple (output, exitcode)."""
(pr, pw) = os.pipe()
pid = os.fork()
if pid == 0:
os.close(pr)
os.dup2(pw, 1)
devnull = os.open("/dev/null", os.O_RDWR)
os.dup2(devnull, 2)
os.execvp(cmd[0], cmd)
os._exit(20)
os.close(pw)
f = os.fdopen(pr)
output = f.readlines()
f.close()
(deadpid, status) = os.waitpid(pid, 0)
return (output, status >> 8)
def nice_size(size):
"""Return a nicely-formatted file size."""
sizes = [ "B", "KiB", "MiB", "GiB", "TiB" ]
s = "empty"
for n in range(len(sizes)):
r = 1024 ** n
if size > r: s = str(size / r) + sizes[n]
return s
def get_metadata(cache, image, st):
"""Get the type and size of an image, returning the tuple
(type, width, height). Returns None for type on failure."""
if cache.has_key(image):
(time, md) = cache[image]
if time == st.st_mtime: return md
(output, status) = capture(("identify", image))
if status != 0 or len(output) != 1: return (None, 0, 0)
ident = output[0].strip()[len(image) + 1:].split(" ", 2)
size = ident[1]
pos = size.find("+")
if pos != -1: size = size[:pos]
wh = size.split("x")
if len(wh) != 2: return (None, 0, 0)
md = (ident[0], int(wh[0]), int(wh[1]))
cache[image] = (st.st_mtime, md)
return md
def make_thumbnail(image, thumbname, width, height):
"""Generate a thumbnail for an image. Return 1 on success, 0 on
failure."""
factor = 1.0
if width > thumb_width or height > thumb_height:
wfactor = thumb_width / (1.0 * width)
hfactor = thumb_height / (1.0 * height)
factor = min(wfactor, hfactor)
width = int(factor * width)
height = int(factor * height)
size = str(width) + "x" + str(height)
border = str((thumb_width - width) / 2) + "x" + str((thumb_height - height) / 2)
cmd = ["convert", "-size", size, image,
"-resize", size,
"-border", border,
"-bordercolor", "grey",
thumbname]
(output, status) = capture(cmd)
return status == 0
def die(error):
print >>sys.stderr, error
sys.exit(20)
def index_name(i):
if i == 0:
s = ""
else:
s = str(i)
return "index" + s + ".html"
def make_index(dir, images, dirs, others, pages, page):
iname = thumbs_dir + "/" + index_name(0)
body = []
try:
f = open("../" + iname)
f.close()
body.append('Parent directory
')
except:
pass
if len(dirs) > 0:
body.append('Subdirectories
')
body.append('')
for d in dirs:
try:
f = open(d + "/" + thumbs_dir + "/title")
title = ": " + f.read().strip()
f.close()
except IOError:
title = ""
body.append('- ' + d + '' + title + '
')
body.append('
')
if len(others) > 0:
body.append('Other files
')
body.append('')
for (file, size) in others:
body.append('- ' + file + '
')
body.append('
')
if len(images) > 0:
body.append('Images
')
(first, last) = pages[page]
if len(pages) != 1:
bits = []
for i in range(len(pages)):
(f, l) = pages[i]
r = str(f + 1) + '-' + str(l)
if i == page:
bits.append(r)
else:
bits.append('' + r + '')
body.append('' + " ".join(bits) + '
')
body.append('')
for (image, size, width, height) in images[first:last]:
label = image
i = label.rindex(".")
if i > 0: label = label[:i]
if len(label) > 15: label = label[:15] + "..."
body.append('
')
body.append('
')
body.append('
')
body.append('
')
body.append('
')
body.append('
')
body.append('
')
title = "Directory " + dir
try:
title = open(thumbs_dir + "/title").read().strip()
except IOError:
pass
blurb = ""
try:
blurb = open(thumbs_dir + "/blurb").read().strip()
except IOError:
pass
if template_file is not None:
f = open(template_file)
html = f.read()
f.close()
else:
html = default_template
html = html.replace("__TITLE__", title)
html = html.replace("__BLURB__", blurb)
html = html.replace("__BODY__", "\n".join(body))
f = open(thumbs_dir + "/" + index_name(page), "w")
f.write(html)
f.close()
def process_dir(dir):
if dir[-1] == "/": dir = dir[:-1]
print "Processing " + dir + "..."
oldcwd = os.getcwd()
os.chdir(dir)
try:
os.mkdir(thumbs_dir)
except OSError:
pass
try:
f = open(thumbs_dir + "/metadata_cache")
metadata_cache = pickle.load(f)
f.close()
except IOError:
metadata_cache = {}
images = []
dirs = []
others = []
for file in os.listdir("."):
if file == "g" or file.startswith("."):
continue
if any([fnmatch.fnmatch(file, pat) for pat in ignore_patterns]):
continue
st = os.stat(file)
if stat.S_ISDIR(st.st_mode):
dirs.append(file)
continue
elif not stat.S_ISREG(st.st_mode):
continue
ext = file.split(".")[-1]
if not ext.lower() in recognised_extensions:
others.append((file, st.st_size))
continue
(type, width, height) = get_metadata(metadata_cache, file, st)
if type is None: continue
need_thumbnail = 1
thumbname = thumbs_dir + "/thumb-" + file + ".jpg"
try:
tst = os.stat(thumbname)
if tst.st_mtime > st.st_mtime: need_thumbnail = 0
except OSError:
pass
if need_thumbnail:
print "Thumbnailing " + file + "...",
if make_thumbnail(file, thumbname, width, height):
print "OK"
else:
print "failed"
continue
images.append((file, st.st_size, width, height))
images.sort()
dirs.sort()
others.sort()
num_images = len(images)
pages = []
for i in range((num_images / thumbs_per_page) + 1):
f = i * thumbs_per_page
l = f + thumbs_per_page
if l > num_images:
l = num_images
pages.append((f, l))
for i in range(len(pages)):
make_index(dir, images, dirs, others, pages, i)
f = open(thumbs_dir + "/metadata_cache", "w")
pickle.dump(metadata_cache, f)
f.close()
for dir in dirs: process_dir(dir)
os.chdir(oldcwd)
def main(argv):
try:
(optlist, argv) = getopt.getopt(argv[1:], "t:X:")
except getopt.GetoptError, s:
die(s)
if len(argv) < 1:
die("usage: gallstone [-t template] [-X ignore-pattern] [...]")
template_dir = os.getenv("HOME") + "/.gallstone"
for o, a in optlist:
if o == "-t":
global template_file
template_file = template_dir + "/" + a
elif o == "-X":
ignore_patterns.append(a)
for x in argv:
process_dir(x)
if __name__ == "__main__": main(sys.argv)