From: W. Trevor King Date: Tue, 21 Feb 2012 15:30:46 +0000 (-0500) Subject: Update gallery.py to run as a CGI server (bump to v0.4). X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=983423e90c0c19c8a161a8bd464d3eb35c54eb3a;p=blog.git Update gallery.py to run as a CGI server (bump to v0.4). --- diff --git a/posts/gallery/gallery.py b/posts/gallery/gallery.py index 0db8ffa..b84a0cd 100755 --- a/posts/gallery/gallery.py +++ b/posts/gallery/gallery.py @@ -1,6 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/env python # -# Copyright (C) 2010 W. Trevor King +# Copyright (C) 2010-2011 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 @@ -17,7 +17,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ -Generate HTML gallery pages for a picture directory organized along:: +CGI gallery server for a picture directory organized along:: pics |-- some_directory @@ -37,39 +37,50 @@ With:: 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 . +See RFC 3875 for more details on the the Common Gateway Interface. """ +# import logging first so we can timestamp other module imports import logging + + +LOG = logging.getLogger('gallery') +_ch = logging.StreamHandler() +_ch.setLevel(logging.DEBUG) +_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') +_ch.setFormatter(_formatter) +LOG.addHandler(_ch) +#LOG.setLevel(logging.DEBUG) +LOG.setLevel(logging.WARNING) + +LOG.debug('importing math') +import math +LOG.debug('importing os') import os +LOG.debug('importing os.path') import os.path +LOG.debug('importing random') +import random +LOG.debug('importing re') +import re +LOG.debug('importing subprocess') from subprocess import Popen, PIPE +LOG.debug('importing sys') import sys -__version__ = '0.3' -LOG = logging +LOG.debug('parsing class') + + +__version__ = '0.4' + +IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.tif', '.tiff', '.png', '.gif'] +VIDEO_EXTENSIONS = ['.mov', '.mp4', '.ogv'] +RESPONSES = { # httplib takes half a second to load + 200: 'OK', + 404: 'Not Found', + } class CommandError(Exception): @@ -92,223 +103,531 @@ def invoke(args, stdin=None, stdout=PIPE, stderr=PIPE, expect=(0,), """ if cwd == None: cwd = '.' - LOG.debug('%s$ %s' % (cwd, ' '.join(args))) + LOG.debug('{}$ {}'.format(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)) + LOG.debug('{:d}\n{}{}'.format(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']: + for extension in IMAGE_EXTENSIONS: if filename.lower().endswith(extension): return True return False -def picture_base(filename): +def image_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 + +class CGIGalleryServer (object): + def __init__(self, base_path='/var/www/localhost/htdocs/gallery/', + base_url='/cgi-bin/gallery.py', + cache_path='/tmp/gallery-cache/'): + self._base_path = base_path + self._base_url = base_url + self._cache_path = cache_path + self._url_regexp = re.compile('^[a-z0-9._/-]*$') + self._rows = 3 + self._columns = 3 + self.header = [] + self.footer = [] + + def _http_header(self, mime='text/html', status=200): + msg = RESPONSES[status] + header = ['Status: {:d} {}'.format(status, msg)] + if mime.startswith('text/'): + charset = '; charset=UTF-8' + else: + charset = '' + header.append('Content-type: {}{}'.format(mime, charset)) + return '\n'.join(header) + + def _response(self, header=None, content='

It works!

'): + if header is None: + header = self._http_header() + sys.stdout.write(header) + sys.stdout.write('\n\n') + sys.stdout.write(content) + sys.exit(0) + + def _response_stream(self, header=None, content=None, chunk_size=1024): + LOG.debug('streaming response') + if header is None: + header = self._http_header() + sys.stdout.write(header) + sys.stdout.write('\n\n') + sys.stdout.flush() # flush headers + while True: + chunk = content.read(chunk_size) + if not chunk: + break + sys.stdout.write(chunk) + sys.exit(0) + + def _error(self, status=404, content=None): + header = self._http_header(status=status) + if content is None: + content = RESPONSES[status] + self._response(header=header, content=content) + + def validate_url(self, url): + if url is None: + return + elif (not self._url_regexp.match(url) or + url.startswith('/') or + '..' in url + ): + LOG.error('invalid url') + self._error(404) + path = os.path.join(self._base_path, url) + if os.path.exists(path) and not os.path.isdir(path): + LOG.error('nonexstand directory') + self._error(404) + + def serve(self, url, page=0): + LOG.info('serving url {} (page {})'.format(url, page)) + if url is None: + self.index() + elif url.endswith('random'): + self.random(url=url, max_width=500, max_height=500) + elif self.is_cached(url=url): + self.cached(url=url) + elif url.endswith('.png'): + self.thumb(url=url) + else: + self.page(url=url, page=page) + self.validate_url(url=url) + LOG.error('unexpected url type') + self._error(404) + + def _url(self, path): + relpath = os.path.relpath( + os.path.join(self._base_path, path), self._base_path) + if relpath == '.': + relpath = '' + elif path.endswith('/'): + relpath += '/' + return '{}{}'.format(self._base_url, relpath) + + def _label(self, path): + dirname,base = os.path.split(path) + if not base: # directory path ending with '/' + dirname,base = os.path.split(dirname) + return base.replace('_', ' ').title() + + def _link(self, path, text=None): + if text is None: + text = self._label(path) + return '{}'.format(self._url(path), text) + + def _subdirs(self, path): + try: + order = [d.strip() for d in + open(os.path.join(path, '_order')).readlines()] + except IOError: + order = [] + dirs = sorted(os.listdir(path)) + start = [] + for d in order: + if d in dirs: + start.append(d) + dirs.remove(d) + for d in start + dirs: + dirpath = os.path.join(path, d) + if os.path.isdir(dirpath): + yield dirpath + + def _images(self, path): + for p in sorted(os.listdir(path)): + if p.startswith('.') or p.endswith('~'): + continue + picture_path = os.path.join(path, p) + if is_picture(picture_path): + yield picture_path + + def index(self): + LOG.debug('index page') + return self._directory(self._base_path) + + def _thumb(self, image, max_width=None, max_height=None): + if not os.path.exists(self._cache_path): + os.makedirs(self._cache_path) + dirname,filename = os.path.split(image) + reldir = os.path.relpath(dirname, self._base_path) + cache_dir = os.path.join(self._cache_path, reldir) + if not os.path.isdir(cache_dir): + os.makedirs(cache_dir) + extension = '-{:d}-{:d}.png'.format(max_width, max_height) + thumb_filename = image_base(filename)+extension + thumb_url = os.path.join(dirname, thumb_filename) + thumb_path = os.path.join(cache_dir, thumb_filename) + image_path = os.path.join(self._base_path, image) + if not os.path.isfile(image_path): + LOG.error('image path for thumbnail does not exist') + return self._error(404) + if (not os.path.isfile(thumb_path) + or os.path.getmtime(image_path) > os.path.getmtime(thumb_path)): + invoke(['convert', '-format', 'png', '-strip', '-quality', '95', + image_path, + '-thumbnail', '{:d}x{:d}'.format(max_width, max_height), + thumb_path]) + return thumb_url + + def _mp4(self, video, *args): + if not video.endswith('.mov'): + LOG.error("can't translate {} to MPEGv4".format(video)) + dirname,filename = os.path.split(video) + mp4_filename = image_base(filename) + '.mp4' + reldir = os.path.relpath(dirname, self._base_path) + cache_dir = os.path.join(self._cache_path, reldir) + if not os.path.isdir(cache_dir): + os.makedirs(cache_dir) + mp4_url = os.path.join(dirname, mp4_filename) + mp4_path = os.path.join(cache_dir, mp4_filename) + if not os.path.isfile(video): + LOG.error('source video path does not exist') + return self._error(404) + if (not os.path.isfile(mp4_path) + or os.path.getmtime(video) > os.path.getmtime(mp4_path)): + arg = ['ffmpeg', '-i', video, '-acodec', 'libfaac', '-aq', '200', + '-ac', '1', '-s', '640x480', '-vcodec', 'libx264', + '-preset', 'slower', '-vpre', 'ipod640', '-b', '800k', + '-bt', '800k', '-aspect', '640:480', '-threads', '0'] + arg.extend(args) + arg.append(mp4_path) + invoke(arg) + return self._url(mp4_url) + + def _ogv(self, video, *args): + if not video.endswith('.mov'): + LOG.error("can't translate {} to Ogg Video".format(video)) + dirname,filename = os.path.split(video) + ogv_filename = image_base(filename) + '.ogv' + reldir = os.path.relpath(dirname, self._base_path) + cache_dir = os.path.join(self._cache_path, reldir) + if not os.path.isdir(cache_dir): + os.makedirs(cache_dir) + ogv_url = os.path.join(dirname, ogv_filename) + ogv_path = os.path.join(cache_dir, ogv_filename) + if not os.path.isfile(video): + LOG.error('source video path does not exist') + return self._error(404) + if (not os.path.isfile(ogv_path) + or os.path.getmtime(video) > os.path.getmtime(ogv_path)): + arg = ['ffmpeg2theora', '--optimize'] + arg.extend(args) + arg.extend(['--output', ogv_path, video]) + invoke(arg) + return self._url(ogv_url) + + def _get_image_caption(self, path): + caption_path = path + '.txt' + try: + return open(caption_path, 'r').read() + except IOError: + return None + + def _get_image_video(self, path, fallback=None): + base_path = image_base(path) + for extension in VIDEO_EXTENSIONS: + video_path = base_path + extension + if os.path.isfile(video_path): + return self._video(video_path, fallback=fallback) + return None + + def _captioned_video(self, path, href=None): + img = self._image(path, max_width=640, max_height=480) + caption = self._get_image_caption(path) + video = self._get_image_video(path, fallback=[img]) + content = [] + if video: + content.extend(video) + if href: + content.append('

{}

'.format( + self._link(path=href, text='gallery page'))) + elif href: + content.append(self._link(path=href, text=img)) + else: + content.append(img) + if caption: + content.append('

{}

'.format(caption)) + return content + + def _video(self, video, fallback=None, **kwargs): + if fallback is None: + fallback = [ + '

Your browser does not support the <video> tag, try', + 'downloading the video and playing it in an external player.', + '

', + ] + fallback = [' '+line for line in fallback] + ogv = self._ogv(video) + mp4 = self._mp4(video) + return [ + '

', + (' ', + '

', + '

Download as', + ' Ogg/Theora/Vorbis or'.format(ogv), + (' Mpeg4/H.264(ConstrainedBaselineProfile)/AAC.' + ).format(mp4), + '

', + ] + + def _image(self, image, **kwargs): + if kwargs: + image = self._thumb(image, **kwargs) + return ''.format(self._url(image)) + + def _image_page(self, image): + return image_base(image) + '/' + + def random(self, url=None, **kwargs): + LOG.debug('random image') + if url.endswith('/random'): + base_dir = os.path.join( + self._base_path, url[:(-len('/random'))]) + elif url == 'random': + base_dir = self._base_path + else: + self._error(404) + images = [] + for dirpath,dirnames,filenames in os.walk(base_dir): + for filename in filenames: + if is_picture(filename): + images.append(os.path.join(dirpath, filename)) + if not images: + self._response(content='

no images to choose from

') + image = random.choice(images) + LOG.debug('selected random image {}'.format(image)) + page = self._image_page(image) + content = self._captioned_video(path=image, href=page) + self._response(content='\n'.join(content)) + + def is_cached(self, url): + for extension in ['.png', '.mp4', '.ogv']: + if url.endswith(extension): + return True + return False + + def cached(self, url): + LOG.debug('retrieving cached item') + if url.endswith('.png'): + mime = 'image/png' + elif url.endswith('.ogv'): + mime = 'video/ogg' + elif url.endswith('.mp4'): + mime = 'video/mp4' + else: + raise NotImplementedError() + header = self._http_header(mime=mime) + cache_path = os.path.join(self._cache_path, url) + try: + stream = open(cache_path, 'rb') + except IOError, e: + LOG.error('invalid url') + LOG.error(e) + self._error(404) + if mime in ['video/ogg']: + self._response_stream(header=header, content=stream) + content = stream.read() + self._response(header=header, content=content) + + def page(self, url, page=0): + LOG.debug('HTML page') + if not url.endswith('/'): + LOG.error('HTML page URLs must end with a slash') + self._error(404) + abspath = os.path.join(self._base_path, url) + if os.path.isdir(abspath): + self._directory(path=abspath, page=page) + for extension in IMAGE_EXTENSIONS: + file_path = abspath[:-1] + extension + if os.path.isfile(file_path): + self._page(path=file_path) + LOG.debug('unknown HTML page') + self._error(404) + + def _directory_header(self, path): + relpath = os.path.relpath(path, self._base_path) + crumbs = [] + dirname = relpath + while dirname: + dirname,base = os.path.split(dirname) + if base != '.': + crumbs.insert(0, base) + crumbs.insert(0, '') + links = [None] * len(crumbs) + for i,c in enumerate(crumbs): + if i < len(crumbs)-1: + if i == 0: + links[i] = self._link(self._base_path, 'Gallery') + else: + relpath = '/'.join(crumbs[1:i+1]) + '/' + fullpath = os.path.join(self._base_path, relpath) + links[i] = self._link(path=fullpath) 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 i == 0: + links[i] = 'Gallery' + else: + links[i] = self._label(crumbs[i]) + content = ['

{}

'.format(' '.join(links))] + return content + + def _directory_page_navigation(self, path, page, pages): + if pages <= 1: + return [] + prev_page = path + '?pp={:d}'.format((page - 1) % pages + 1) + next_page = path + '?pp={:d}'.format((page + 1) % pages + 1) + return [ + '
', + '

', + self._link(prev_page, 'previous'), + '({:d} of {:d})'.format(page+1, pages), + self._link(next_page, 'next'), + '

', + '
', + ] + + def _directory_subdirs(self, path): + content = [] + dirs = list(self._subdirs(path)) + if dirs: + content.append('
    ') + for d in dirs: + content.append('
  • {}
  • '.format(self._link(d+'/'))) + content.append('
') + return content + + def _directory_images(self, path, images): + content = [''] + column = 0 + for image in images: + page = self._image_page(image) + img = self._image(image, max_width=300, max_height=300) + link = self._link(page, img) + if column == 0: + content.append(' ') + content.extend([ + ' ', + ]) + column += 1 + if column == self._columns: + content.append(' ') + column = 0 + if column != 0: + #content.extend() + content.append(' ') + content.append('
', + ' {}'.format(link), + '
') + return content + + def _directory(self, path, page=0): + LOG.debug('directory page') + images = list(self._images(path)) + images_per_page = self._rows * self._columns + pages = int(math.ceil(float(len(images)) / images_per_page)) or 1 + if page < 0 or page >= pages: + LOG.error( + 'page out of bounds for this gallery 0 <= {:d} < {:d}'.format( + page, pages)) + self._error(404) + first_image = images_per_page * page + images = images[first_image:first_image+images_per_page] + content = [] + content.extend(self.header) + content.extend(self._directory_header(path)) + nav = self._directory_page_navigation(path, page=page, pages=pages) + content.extend(nav) + content.extend(self._directory_subdirs(path)) + content.extend(self._directory_images(path, images=images)) + content.extend(nav) + content.extend(self.footer) + self._response(content='\n'.join(content)) + + def _page(self, path): + LOG.debug('image page') + gallery = os.path.dirname(path) + images = list(self._images(gallery)) + images_per_page = self._rows * self._columns + i = images.index(path) + page = i / images_per_page + gallery_page = '{}/?pp={:d}'.format(gallery, page + 1) + prev_page = self._image_page(images[i - 1]) + next_page = self._image_page(images[(i + 1) % len(images)]) + content = [] + content.extend(self.header) + content.extend([ + '
', + '

', + self._link(prev_page, 'previous'), + self._link(gallery_page, 'all'), + self._link(next_page, 'next'), + '

', + ]) + content.extend(self._captioned_video(path)) + content.append('
') + content.extend(self.footer) + self._response(content='\n'.join(content)) 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) + LOG.debug('entering __main__ loop') + + LOG.debug('importing cgi') + import cgi + LOG.debug('importing cgitb') + import cgitb + + LOG.debug('parsing arguments') + + url = None + page = 0 + if '--url' in sys.argv: + i = sys.argv.index('--url') + try: + url = sys.argv[i+1] + except IndexError: + pass + if '--page' in sys.argv: + i = sys.argv.index('--page') + page = int(sys.argv[i+1]) + else: + cgitb.enable() + #cgitb.enable(display=0, logdir="/tmp/") + data = cgi.FieldStorage() + if 'p' in data: + p = data['p'] + if isinstance(p, list): + p = p[0] + url = p.value + if 'pp' in data: + try: + page = int(data['pp'].value) - 1 + except ValueError: + pass + + s = CGIGalleryServer( + base_path='/var/www/localhost/htdocs/gallery/', + base_url='/gallery/') + shared = '/var/www/localhost/htdocs/shared/' + s.header = [open(os.path.join(shared, 'header.shtml'), 'r').read()] + s.footer = [open(os.path.join(shared, 'footer.shtml'), 'r').read()] + s.serve(url=url, page=page)