#!/usr/bin/python # # Copyright (C) 2010 W. Trevor King # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ Generate HTML gallery pages for a picture directory organized along:: pics |-- some_directory | |-- a_picture.jpg | |-- another_picture.jpg | |-- ... |-- another_directory | |-- a_picture.jpg | |-- captioned_picture.jpg | |-- captioned_picture.jpg.txt |-- ... With:: pics$ gallery.py some_directory another_directory Note that you can store a caption for ```` as plain text in ``.txt``. The resulting gallery pages will be:: pics |-- some_directory | |-- a_picture.jpg | |-- another_picture.jpg | |-- ... | |-- -> | |-- | |-- | |-- | |-- | | |-- a_picture.png | | |-- another_picture.png | | |-- ... | |-- | | |-- a_picture.html | | |-- another_picture.html | | |-- ... |-- ... So you'll probably want to symlink index.html to . """ import logging import os import os.path from subprocess import Popen, PIPE import sys __version__ = '0.3' LOG = logging class CommandError(Exception): def __init__(self, command, status, stdout=None, stderr=None): strerror = ['Command failed (%d):\n %s\n' % (status, stderr), 'while executing\n %s' % str(command)] Exception.__init__(self, '\n'.join(strerror)) self.command = command self.status = status self.stdout = stdout self.stderr = stderr def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,), cwd=None, encoding=None): """ expect should be a tuple of allowed exit codes. cwd should be the directory from which the command will be executed. When unicode_output == True, convert stdout and stdin strings to unicode before returing them. """ if cwd == None: cwd = '.' LOG.debug('%s$ %s' % (cwd, ' '.join(args))) try : q = Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr, cwd=cwd) except OSError, e: raise CommandError(args, status=e.args[0], stderr=e) stdout,stderr = q.communicate(input=stdin) status = q.wait() LOG.debug('%d\n%s%s' % (status, stdout, stderr)) if status not in expect: raise CommandError(args, status, stdout, stderr) return status, stdout, stderr def is_picture(filename): for extension in ['.jpg', '.jpeg', '.tif', '.tiff', '.png', '.gif']: if filename.lower().endswith(extension): return True return False def picture_base(filename): parts = filename.rsplit('.', 1) assert len(parts) == 2, parts return parts[0] def pictures(picdir): return sorted([p for p in os.listdir(picdir) if is_picture(p)]) def make_thumbs(picdir, pictures, thumbdir, max_height, max_width, force=False): fullthumbdir = os.path.join(picdir, thumbdir) if not os.path.exists(fullthumbdir): os.mkdir(fullthumbdir) if force == False: new_pictures = [] for p in pictures: thumb_p = os.path.join(picdir, thumbdir, picture_base(p)+'.png') if (not os.path.exists(thumb_p) or os.path.getmtime(p) > os.path.getmtime(thumb_p)): new_pictures.append(p) if len(new_pictures) > 0: log.info(' making %d thumbnails for %s' % (len(new_pictures), picdir)) invoke(['mogrify', '-format', 'png', '-strip', '-quality', '95', '-path', thumbdir, '-thumbnail', '%dx%d' % (max_width, max_height), ]+new_pictures, cwd=picdir) return [os.path.join(thumbdir, picture_base(p)+'.png') for p in pictures] def page_header(): return '\n'.join([ '', '', '']) def page_footer(): return '\n'.join([ '', '', '']) def pagename(pic): return picture_base(pic) + '.html' def make_page(picdir, gallery, pagedir, pic, previous_pic=None, next_pic=None, force=False): fullpagedir = os.path.join(picdir, pagedir) name = pagename(pic) if not os.path.exists(fullpagedir): os.mkdir(fullpagedir) page_p = os.path.join(fullpagedir, name) captionfile = os.path.join(picdir, pic+'.txt') if (not force and os.path.exists(page_p) and ((not os.path.exists(captionfile)) or (os.path.exists(captionfile) and os.path.getmtime(captionfile) < os.path.getmtime(page_p)))): LOG.info(' skip page for %s' % page_p) return os.path.join(pagedir, name) LOG.info(' make page for %s' % page_p) p = open(page_p, 'w') p.write(page_header()) if os.path.exists(captionfile): caption = open(captionfile, 'r').read() LOG.debug(' found caption %s' % captionfile) else: caption = None p.write('
\n') if previous_pic != None: p.write('previous\n' % pagename(previous_pic)) p.write('all\n' % gallery) if next_pic != None: p.write('next\n' % pagename(next_pic)) p.write('
\n') p.write('\n' % pic) if caption != None: p.write('

%s

\n' % caption) p.write('
\n') p.write(page_footer()) return os.path.join(pagedir, name) def gallery_header(gallery_page_index=None): return '\n'.join([ '', '','']) def gallery_footer(gallery_page_index=None): return '\n'.join([ '', '','']) def make_gallery(picdir, index, gallery, pagedir, thumbdir, cols, rows, height, width, up_link=None, force=False): LOG.info('make gallery for %s' % picdir) pics = pictures(picdir) thumbs = make_thumbs(picdir, pics, thumbdir, height, width, force=force) pages = [] if os.path.exists(os.path.join(picdir, gallery)) and force == False: return pic_thumbs = zip(pics, thumbs) i = 0 gallery_i = 1 # one-indexed g = None while i < len(pic_thumbs): if g == None: gallery_page = os.path.join(picdir, gallery % gallery_i)) LOG.info(' write gallery page %s' % gallery_page) g = open(gallery_page, 'w') g.write(gallery_header(gallery_i)) if up_link != None: up_html = '%s\n' % up_link else: up_html = '' if gallery_i > 1: prev_url = gallery % (gallery_i-1) prev_html = 'previous\n' % prev_url else: prev_html = '' if i + rows*cols < len(pic_thumbs): next_url = gallery % (gallery_i+1) next_html = 'next\n' % next_url else: next_html = '' g.write('
\n') g.write('

%s%s%s

\n' % (prev_html, up_html, next_html)) g.write('\n') column = 0 row = 0 LOG.info('placing picture %d of %d' % (i+1, len(pic_thumbs))) pic,thumb = pic_thumbs[i] prev = next = None if i > 0: prev = pics[i-1] if i+1 < len(pics): next = pics[i+1] page = make_page(picdir, gallery % gallery_i, pagedir, pic, prev, next, force=force) if column == 0: g.write(' \n') g.write(' \n') column += 1 if column == cols: g.write(' \n') column = 0 row += 1 if row == rows or i+1 == len(pic_thumbs): g.write('
\n') g.write(' \n' % page) g.write(' %s\n') g.write('
\n') g.write('
\n') g.write(gallery_footer(gallery_i)) g.close() g = None gallery_i += 1 i += 1 if i > 0 and not os.path.exists(index): os.symlink(gallery % 1, index) if __name__ == '__main__': import optparse parser = optparse.OptionParser(usage='%prog [options] PICTURE-DIR ...', epilog=__doc__) parser.format_epilog = lambda formatter : __doc__ parser.add_option('--index', default='index.html', dest='index', help='Name of the index page (symlinked to (%default)') parser.add_option('--gallery', default='gallery-%d.html', dest='gallery', help='Name of the gallery page, must include a %%d. (%default)') parser.add_option('--up-link', default=None, dest='up_link', help='Text for link to gallery parent') parser.add_option('--pagedir', default='./pages', dest='pagedir', help='Relative path from gallery page to page directory (%default)') parser.add_option('--thumbdir', default='./thumbs', dest='thumbdir', help='Relative path from gallery page to thumbnail directory (%default)') parser.add_option('-r', '--rows', default=4, type='int', dest='cols', help='Rows of thumbnails per page (%default)') parser.add_option('-c', '--cols', default=10, type='int', dest='rows', help='Columns of thumbnails per page (%default)') parser.add_option('-H', '--height', default=100, type='int', dest='height', help='Maximum thumbnail height in pixels (%default)') parser.add_option('-W', '--width', default=300, type='int', dest='width', help='Maximum thumbnail width in pixels (%default)') parser.add_option('--force', default=False, dest='force', help='Regenerate existing gallery files', action='store_true') parser.add_option('-v', '--verbose', default=0, dest='verbose', help='Increment verbosity', action='count') options,args = parser.parse_args() logging.basicConfig(level=level) levels = level = [ logging.WARNING, logging.INFO, logging.DEBUG][options.verbose] for picdir in args: kwargs = {} for attr in ['index', 'gallery', 'up_link', 'pagedir', 'thumbdir', 'cols', 'rows', 'height', 'width', 'force']: kwargs[attr] = getattr(options, attr) make_gallery(picdir, **kwargs)