--- a/degal.py Thu Dec 20 17:42:04 2007 +0000
+++ b/degal.py Fri Dec 21 20:36:03 2007 +0000
@@ -21,647 +21,11 @@
#
import os.path, os
-import urllib
-import logging
-import string
-from datetime import datetime
-import struct
-import base64
-import shelve
-
-import PIL
-import PIL.Image
-
-import utils
-
-__version__ = '0.2'
-
-TEMPLATE_DIR='templates'
-TEMPLATE_EXT='html'
-
-logging.basicConfig(
- level=logging.INFO,
- format="%(message)s",
-# format="%(name)8s %(levelname)8s %(lineno)3d %(message)s",
-
-)
-
-tpl = logging.getLogger('tpl')
-index = logging.getLogger('index')
-render = logging.getLogger('render')
-
-tpl.setLevel(logging.WARNING)
-
-#for l in (tpl, index, prepare, render) :
-# l.setLevel(logging.DEBUG)
-
-def readFile (path) :
- fo = open(path, 'r')
- data = fo.read()
- fo.close()
-
- return data
-
-class Template (object) :
- GLOBALS = dict(
- VERSION=__version__,
- )
-
- def __init__ (self, name) :
- self.name = name
- self.path = os.path.join(TEMPLATE_DIR, "%s.%s" % (name, TEMPLATE_EXT))
-
- tpl.debug("Template %s at %s", name, self.path)
-
- self.content = readFile(self.path)
-
- def render (self, **vars) :
- content = self.content
-
- vars.update(self.GLOBALS)
-
- for name, value in vars.iteritems() :
- content = content.replace('<!-- %s -->' % name, str(value))
-
- return content
-
- def renderTo (self, path, **vars) :
- tpl.info("Render %s to %s", self.name, path)
-
- fo = open(path, 'w')
- fo.write(self.render(**vars))
- fo.close()
-
-gallery_tpl = Template('gallery')
-image_tpl = Template('image')
-
-IMAGE_EXTS = ('jpg', 'jpeg', 'png', 'gif', 'bmp')
-
-THUMB_DIR = 'thumbs'
-PREVIEW_DIR = 'previews'
-TITLE_FILE = 'title.txt'
-
-THUMB_GEOM = (160, 120)
-PREVIEW_GEOM = (640, 480)
-
-DEFAULT_TITLE = 'Image gallery'
-
-# how many image/page
-IMAGE_COUNT = 50
-
-def isImage (fname) :
- """
- Is the given filename likely to be an image file?
- """
-
- fname = fname.lower()
- base, ext = os.path.splitext(fname)
- ext = ext.lstrip('.')
-
- return ext in IMAGE_EXTS
-
-def link (url, title) :
- """
- Returns an <a href=""></a> link tag with the given values
- """
-
- return "<a href='%s'>%s</a>" % (urllib.quote(url), title)
-
-def dirUp (count=1) :
- """
- Returns a relative path to the directly count levels above the current one
- """
-
- if not count :
- return '.'
-
- return os.path.join(*(['..']*count))
-
-def readTitleDescr (path) :
- """
- Read a title.txt or <imgname>.txt file
- """
-
- if os.path.exists(path) :
- content = readFile(path)
-
- if '---' in content :
- title, descr = content.split('---', 1)
- else :
- title, descr = content, ''
-
- return title.strip(), descr.strip()
-
- return None, None
-
-class Folder (object) :
- def __init__ (self, name='.', parent=None) :
- # the directory name
- self.name = name
-
- # our parent Folder, or None
- self.parent = parent
-
- # the path to this dir, as a relative path to the root of the image gallery, always starts with .
- if parent and name :
- self.path = parent.pathFor(name)
- else :
- self.path = name
-
- # the url-path to the index.html file
- self.html_path = self.path
-
- # dict of fname -> Folder
- self.subdirs = {}
-
- # dict of fname -> Image
- self.images = {}
-
- # our human-friendly title
- self.title = None
-
- # our long-winded description
- self.descr = ''
-
- # is this folder non-empty?
- self.alive = None
-
- # self.images.values(), but sorted by filename
- self.sorted_images = []
-
- # the ShortURL key to this dir
- self.shorturl_code = None
-
- # were we filtered out?
- self.filtered = False
-
- def pathFor (self, *fnames) :
- """
- Return a root-relative path to the given path inside this dir
- """
- return os.path.join(self.path, *fnames)
-
- def index (self, filters=None) :
- """
- Look for other dirs and images inside this dir. Filters must be either None,
- whereupon all files will be included, or a dict of {filename -> next_filter}.
- If given, only filenames that are present in the dict will be indexed, and in
- the case of dirs, the next_filter will be passed on to that Folder's index
- method.
- """
-
- index.info("Indexing %s", self.path)
-
- if filters :
- self.filtered = True
-
- # iterate through listdir
- for fname in os.listdir(self.path) :
- # the full filesystem path to it
- fpath = self.pathFor(fname)
-
- # ignore dotfiles
- if fname.startswith('.') :
- index.debug("Skipping dotfile %s", fname)
- continue
-
- # apply filters
- if filters :
- if fname in filters :
- next_filter = filters[fname]
- else :
- index.debug("Skip `%s' as we have a filter", fname)
- continue
- else :
- next_filter = None
-
- # recurse into subdirs, but not thumbs/previews
- if os.path.isdir(fpath) and fname not in (THUMB_DIR, PREVIEW_DIR) :
- index.debug("Found subdir %s", fpath)
- f = self.subdirs[fname] = Folder(fname, self)
- if f.index(next_filter) : # recursion
- # if a subdir is alive, we are alive as well
- self.alive = True
-
- # handle images
- elif os.path.isfile(fpath) and isImage(fname) :
- index.debug("Found image %s", fname)
- self.images[fname] = Image(self, fname)
-
- # ignore everything else
- else :
- index.debug("Ignoring file %s", fname)
-
- # sort and link the images
- if self.images :
- self.alive = True
-
- # sort the images
- fnames = self.images.keys()
- fnames.sort()
-
- prev = None
-
- # link
- for fname in fnames :
- img = self.images[fname]
-
- img.prev = prev
-
- if prev :
- prev.next = img
-
- prev = img
-
- # add to the sorted images list
- self.sorted_images.append(img)
-
- return self.alive
-
- def getObjInfo (self) :
- """
- Metadata for shorturls2.db
- """
- return 'dir', self.path, 'index'
-
- def linkTag (self) :
- """
- A text-link to this dir
- """
-
- return link(self.path, self.title)
-
- def breadcrumb (self) :
- """
- Returns a [(fname, title)] list of this dir's parent dirs
- """
-
- f = self
- b = []
- d = 0
-
- while f :
- b.insert(0, (dirUp(d), f.title))
-
- d += 1
- f = f.parent
-
- return b
-
- def inRoot (self, *fnames) :
- """
- Return a relative URL from this dir to the given path in the root dir
- """
-
- c = len(self.path.split('/')) - 1
-
- return os.path.join(*((['..']*c) + list(fnames)))
-
- def _page_fname (self, page) :
- assert page >= 0
-
- if page > 0 :
- return 'index_%d.html' % page
- else :
- return 'index.html'
-
- def render (self) :
- """
- Render the index.html, Images, and recurse into subdirs
- """
-
- # ded folders are skipped
- if not self.alive :
- render.info("Skipping dir %s", self.path)
- return
-
- # if this dir's contents were filtered out, then we can't render the index.html, as we aren't aware of all the images in here
- if self.filtered :
- render.warning("Dir `%s' contents were filtered, so we won't render the gallery index again", self.path)
+from optparse import OptionParser
- else :
- # create the thumb/preview dirs if needed
- for dir in (THUMB_DIR, PREVIEW_DIR) :
- path = self.pathFor(dir)
-
- if not os.path.isdir(path) :
- render.info("Creating dir %s", path)
- os.mkdir(path)
-
- # figure out our title
- title_path = self.pathFor(TITLE_FILE)
-
- title, descr = readTitleDescr(title_path)
-
- if title :
- self.title = title
- self.descr = descr
-
- # default title for the root dir
- elif self.name == '.' :
- self.title = 'Index'
-
- else :
- self.title = self.name
-
- # sort the subdirs
- subdirs = self.subdirs.items()
- subdirs.sort()
-
- # generate the <a href=""></a>'s for the subdirs
- subdir_linkTags = [link(f.name, f.title) for fname, f in subdirs if f.alive]
-
- # stick them into a list
- if subdir_linkTags :
- directories = "<ul>\n\t<li>%s</li>\n</ul>" % "</li>\n\t<li>".join(subdir_linkTags)
- else :
- directories = ''
-
- render.info("Rendering %s", self.path)
-
- # paginate!
- images = self.sorted_images
- image_count = len(images)
- pages = []
-
- while images :
- pages.append(images[:IMAGE_COUNT])
- images = images[IMAGE_COUNT:]
-
- pagination_required = len(pages) > 1
-
- if pagination_required :
- render.info("Index split into %d pages of %d images each", len(pages), IMAGE_COUNT)
-
- for cur_page, page in enumerate(pages) :
- if pagination_required :
- pagination = "<ul>" # <li>Showing Images %d - %d of %d</li>" % (cur_page*IMAGE_COUNT, cur_page*IMAGE_COUNT+len(page), image_count)
-
- if cur_page > 0 :
- pagination += '<li><a href="%s">« Prev</a></li>' % (self._page_fname(cur_page - 1))
- else :
- pagination += '<li><span>« Prev</span></li>'
-
- for x in xrange(0, len(pages)) :
- if x == cur_page :
- pagination += '<li><strong>%s</strong></li>' % (x + 1)
- else :
- pagination += '<li><a href="%s">%s</a></li>' % (self._page_fname(x), x+1)
-
- if cur_page < len(pages) - 1 :
- pagination += '<li><a href="%s">Next »</a></li>' % (self._page_fname(cur_page + 1))
- else :
- pagination += '<li><span>Next »</span></li>'
-
- pagination += "</ul>"
- shorturl = "%s/%s" % (self.shorturl_code, cur_page+1)
- else :
- pagination = ''
- shorturl = self.shorturl_code
-
- # render to index.html
- gallery_tpl.renderTo(self.pathFor(self._page_fname(cur_page)),
- STYLE_URL=self.inRoot('style.css'),
- BREADCRUMB=" » ".join([link(u, t) for (u, t) in self.breadcrumb()]),
- TITLE=self.title,
- DIRECTORIES=directories,
- PAGINATION=pagination,
- CONTENT="".join([i.thumbImgTag() for i in page]),
- DESCR=self.descr,
- SHORTURL=self.inRoot('s', shorturl),
- SHORTURL_CODE=shorturl,
- )
-
- # render images
- for img in self.images.itervalues() :
- img.render()
-
- # recurse into subdirs
- for dir in self.subdirs.itervalues() :
- dir.render()
-
-class Image (object) :
- def __init__ (self, dir, name) :
- # the image filename, e.g. DSC3948.JPG
- self.name = name
-
- # the Folder object that we are in
- self.dir = dir
-
- # the relative path from the root to us
- self.path = dir.pathFor(name)
-
- # the basename+ext, e.g. DSCR3948, .JPG
- self.base_name, self.ext = os.path.splitext(name)
-
- # the root-relative paths to the thumb and preview images
- self.thumb_path = self.dir.pathFor(THUMB_DIR, self.name)
- self.preview_path = self.dir.pathFor(PREVIEW_DIR, self.name)
-
- # our user-friendly title
- self.title = name
-
- # our long-winded description
- self.descr = ''
-
- # the image before and after us, both may be None
- self.prev = self.next = None
-
- # the name of the .html gallery view thing for this image, *always* self.name + ".html"
- self.html_name = self.name + ".html"
-
- # the root-relative path to the gallery view
- self.html_path = self.dir.pathFor(self.html_name)
-
- #
- # Figured out after prepare
- #
-
- # (w, h) tuple
- self.img_size = None
-
- # the ShortURL code for this image
- self.shorturl_code = None
-
- # what to use in the rendered templates, intended to be overridden by subclasses
- self.series_act = "add"
- self.series_verb = "Add to"
-
- def getObjInfo (self) :
- """
- Metadata for shorturl2.db
- """
- return 'img', self.dir.path, self.name
-
- def thumbImgTag (self) :
- """
- a <a><img /></a> of this image's thumbnail. Path relative to directory we are in
- """
- return link(self.html_name, "<img src='%s' alt='%s' title='%s' />" % (os.path.join(THUMB_DIR, self.name), self.descr, self.title))
+from lib import folder, shorturl
- def previewImgTag (self) :
- """
- a <a><img /></a> of this image's preview. Path relative to directory we are in
- """
- return link(self.name, "<img src='%s' alt='%s' title='%s' />" % (os.path.join(PREVIEW_DIR, self.name), self.descr, self.title))
-
- def linkTag (self) :
- """
- a <a></a> text-link to this image
- """
- return link(self.html_name, self.title)
-
- def breadcrumb (self) :
- """
- Returns a [(fname, title)] list of this image's parents
- """
-
- f = self.dir
- b = [(self.html_name, self.title)]
- d = 0
-
- while f :
- b.insert(0, (dirUp(d), f.title))
-
- d += 1
- f = f.parent
-
- return b
-
- def render (self) :
- """
- Write out the .html file
- """
-
- # stat the image file to get the filesize and mtime
- st = os.stat(self.path)
-
- self.filesize = st.st_size
- self.timestamp = st.st_mtime
-
- # open the image in PIL to get image attributes + generate thumbnails
- img = PIL.Image.open(self.path)
-
- self.img_size = img.size
-
- for out_path, geom in ((self.thumb_path, THUMB_GEOM), (self.preview_path, PREVIEW_GEOM)) :
- # if it doesn't exist, or it's older than the image itself, generate
- if not (os.path.exists(out_path) and os.stat(out_path).st_mtime > self.timestamp) :
- render.info("Create thumbnailed image at %s with geom %s", out_path, geom)
-
- # XXX: is this the most efficient way to do this?
- out_img = img.copy()
- out_img.thumbnail(geom, resample=True)
- out_img.save(out_path)
-
- # look for the metadata file
- title_path = self.dir.pathFor(self.base_name + '.txt')
-
- title, descr = readTitleDescr(title_path)
-
- if title :
- self.title = title
- self.descr = descr
-
- render.info("Rendering image %s", self.path)
-
- image_tpl.renderTo(self.html_path,
- STYLE_URL=self.dir.inRoot('style.css'),
- UP_URL=('.'),
- PREV_URL=(self.prev and self.prev.html_name or ''),
- NEXT_URL=(self.next and self.next.html_name or ''),
- FILE=self.name,
- BREADCRUMB=" » ".join([link(u, t) for u, t in self.breadcrumb()]),
- TITLE=self.title,
- PREVIOUS_THUMB=(self.prev and self.prev.thumbImgTag() or ''),
- IMAGE=self.previewImgTag(),
- NEXT_THUMB=(self.next and self.next.thumbImgTag() or ''),
- DESCRIPTION=self.descr,
- IMGSIZE="%dx%d" % self.img_size,
- FILESIZE=fmtFilesize(self.filesize),
- TIMESTAMP=fmtTimestamp(self.timestamp),
- SHORTURL=self.dir.inRoot('s', self.shorturl_code),
- SHORTURL_CODE=self.shorturl_code,
- SERIES_URL=self.dir.inRoot('series/%s/%s' % (self.series_act, self.shorturl_code)),
- SERIES_VERB=self.series_verb,
- )
-
- def __str__ (self) :
- return "Image `%s' in `%s'" % (self.name, self.dir.path)
-
-def int2key (id) :
- """
- Turn an integer into a short-as-possible url-safe string
- """
- for type in ('B', 'H', 'I') :
- try :
- return base64.b64encode(struct.pack(type, id), '-_').rstrip('=')
- except struct.error :
- continue
-
- raise Exception("ID overflow: %s" % id)
-
-def updateShorturlDb (root) :
- """
- DeGAL <= 0.2 used a simple key => path mapping, but now we use
- something more structured, key => (type, dirpath, fname), where
-
- type - one of 'img', 'dir'
- dirpath - the path to the directory, e.g. '.', './foobar', './foobar/quux'
- fname - the filename, one of '', 'DSC9839.JPG', 'this.png', etc.
- """
-
- db = shelve.open('shorturls2', 'c', writeback=True)
-
- id = db.get('_id', 1)
-
- dirqueue = [root]
-
- # dict of path -> obj
- paths = {}
-
- index.info("Processing ShortURLs...")
-
- while dirqueue :
- dir = dirqueue.pop(0)
-
- dirqueue.extend(dir.subdirs.itervalues())
-
- if dir.alive :
- paths[dir.path] = dir
-
- for img in dir.images.itervalues() :
- paths[img.path] = img
-
- for key in db.keys() :
- if key.startswith('_') :
- continue
-
- type, dirpath, fname = db[key]
-
- path = os.path.join(dirpath, fname).rstrip('/')
-
- try :
- paths.pop(path).shorturl_code = key
- index.debug("Code for `%s' is %s", path, key)
-
- except KeyError :
- index.debug("Path `%s' in DB does not exist?", path)
-
- for obj in paths.itervalues() :
- key = int2key(id)
- id += 1
-
- index.info("Alloc code `%s' for `%s'", key, obj.html_path)
-
- obj.shorturl_code = key
-
- db[key] = obj.getObjInfo()
-
- db['_id'] = id
- db.close()
-
-def main (targets=()) :
+def main (dir='.', targets=()) :
root_filter = {}
for target in targets :
@@ -670,24 +34,19 @@
if path_part :
if path_part not in f :
f[path_part] = {}
+
f = f[path_part]
-
- index.debug('Filter: %s', root_filter)
- root = Folder()
+ root = folder.Folder(dir)
root.index(root_filter)
- updateShorturlDb(root)
+ shorturl.updateDB(root)
root.render()
-def fmtFilesize (size) :
- return utils.formatbytes(size, forcekb=False, largestonly=True, kiloname='KiB', meganame='MiB', bytename='B', nospace=False)
-
-def fmtTimestamp (ts) :
- return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
-
if __name__ == '__main__' :
- from sys import argv
- argv.pop(0)
-
- main(argv)
-
+ parser = OptionParser(usage="usage: %prog [options] ... [target ...]")
+
+ parser.add_option("-d", "--dir", dest="dir", help="look for images in DIR and write the HTML there", metavar="DIR", default=".")
+
+ options, filter_targets = parser.parse_args()
+
+ main(options.dir, filter_targets)