--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/degal/config.py Thu Jun 04 11:22:52 2009 +0300
@@ -0,0 +1,23 @@
+"""
+ Configuration
+"""
+
+class Configuration (object) :
+ """
+ Various configuration settings
+ """
+
+ # the name of the gallery
+ gallery_title = "Image Gallery"
+
+ # recognized image extensions
+ image_exts = ('jpg', 'jpeg', 'png', 'gif', 'bmp')
+
+ # subdirectory used for generated thumbnails/previews
+ thumb_dir = 'thumbs'
+ preview_dir = 'previews'
+
+ # size of generated thumbnails/previews
+ thumb_size = (160, 120)
+ preview_size = (640, 480)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/degal/filesystem.py Thu Jun 04 11:22:52 2009 +0300
@@ -0,0 +1,322 @@
+"""
+ Filesystem path handling
+"""
+
+import os, os.path
+
+class Node (object) :
+ """
+ A filesystem object is basically just complicated representation of a path.
+
+ On the plus side, it has a parent node and can handle unicode/binary paths.
+ """
+
+ # the binary name
+ fsname = None
+
+ # the unicode name
+ name = None
+
+ def decode_fsname (self, fsname) :
+ """
+ Decode the given raw byte string representing a filesystem name into an user-readable unicode name.
+
+ XXX: currently just hardcoded as utf-8
+ """
+
+ return fsname.decode('utf-8', 'replace')
+
+ def encode_name (self, name) :
+ """
+ Returns a suitable fsname for the given unicode name or strict ASCII str
+
+ XXX: currently just hardcoded as utf-8
+ """
+
+ # this should fail for non-ASCII str
+ return name.encode('utf-8')
+
+ @classmethod
+ def from_node (cls, node) :
+ """
+ Construct from a Node object
+ """
+
+ return cls(node.parent, node.fsname, node.name, node.config)
+
+ def __init__ (self, parent, fsname=None, name=None, config=None) :
+ """
+ Initialize the node with a parent and both name/fsname.
+
+ If not given, fsname is encoded from name, or name decoded from fsname, using encode/decode_name.
+
+ If parent is given, but both fsname and name are None, then this node will be equal to the parent.
+ """
+
+ assert not fsname or isinstance(fsname, str)
+
+ if parent and not fsname and not name :
+ # no name given -> we're the same as parent
+ self.parent, self.config, self.fsname, self.name = parent.parent, parent.config, parent.fsname, parent.name
+
+ else :
+ # store
+ self.parent = parent
+
+ # config, either as given, or copy from parent
+ if config :
+ self.config = config
+
+ else :
+ self.config = parent.config
+
+ # fsname
+ if fsname :
+ self.fsname = fsname
+
+ else :
+ self.fsname = self.encode_name(name)
+
+ # name
+ if name :
+ self.name = name
+
+ else :
+ self.name = self.decode_fsname(fsname)
+
+ def subnode (self, name) :
+ """
+ Returns a Node object representing the given name behind this node.
+
+ The name should either be a plain ASCII string or unicode object.
+ """
+
+ return Node(self, name=name)
+
+ @property
+ def path (self) :
+ """
+ Build and return the real filesystem path for this node
+ """
+
+ # build using parent path and our fsname
+ return os.path.join(self.parent.path, self.fsname)
+
+ @property
+ def unicodepath (self) :
+ """
+ Build and return the fake unicode filesystem path for this node
+ """
+
+ # build using parent unicodepath and our name
+ return os.path.join(self.parent.path, self.name)
+
+ def exists (self) :
+ """
+ Tests if this node exists on the physical filesystem
+ """
+
+ return os.path.exists(self.path)
+
+ def is_dir (self) :
+ """
+ Tests if this node represents a directory on the filesystem
+ """
+
+ return os.path.isdir(self.path)
+
+ def is_file (self) :
+ """
+ Tests if this node represents a normal file on the filesystem
+ """
+
+ return os.path.isfile(self.path)
+
+ def __str__ (self) :
+ """
+ Returns the raw filesystem path
+ """
+
+ return self.path
+
+ def __unicode__ (self) :
+ """
+ Returns the human-readable path
+ """
+
+ return self.unicodepath
+
+ def __repr__ (self) :
+ """
+ Returns a str representing this dir
+ """
+
+ return "Node(%r, %r)" % (self.parent.path, self.fsname)
+
+ def __cmp__ (self) :
+ """
+ Comparisons between Nodes
+ """
+
+ return cmp((self.parent, self.name), (other.parent, other.name))
+
+class File (Node) :
+ """
+ A file. Simple, eh?
+ """
+
+ @property
+ def basename (self) :
+ """
+ Returns the "base" part of this file's name, i.e. the filename without the extension
+ """
+
+ basename, _ = os.path.splitext(self.name)
+
+ return basename
+
+ @property
+ def fileext (self) :
+ """
+ Returns the file extension part of the file's name, without any leading dot
+ """
+
+ _, fileext = os.path.splitext(self.name)
+
+ return fileext.rstrip('.')
+
+ def matchext (self, ext_list) :
+ """
+ Tests if this file's extension is part of the recognized list of extensions
+ """
+
+ return (self.fileext.lower() in ext_list)
+
+class Directory (Node) :
+ """
+ A directory is a node that contains other nodes.
+ """
+
+ # a list of (test_func, node_type) tuples for use by children() to build subnodes with
+ NODE_TYPES = None
+
+ def subdir (self, name) :
+ """
+ Returns a Directory object representing the name underneath this dir
+ """
+
+ return Directory(self, name=name)
+
+ def subfile (self, name) :
+ """
+ Returns a File object representing the name underneath this dir
+ """
+
+ return Directory(self, name=name)
+
+ def listdir (self, skip_dotfiles=True) :
+ """
+ Yield a series of raw fsnames for nodes in this dir
+ """
+
+ # expressed
+ return (fsname for fsname in os.listdir(self.path) if not (skip_dotfiles and fsname.startswith('.')))
+
+ def child_nodes (self, skip_dotfiles=True) :
+ """
+ Yield a series of nodes contained in this dir
+ """
+
+ return (Node(self, fsname) for fsname in self.listdir(skip_dotfiles))
+
+ def __iter__ (self) :
+ """
+ Iterating over a Directory yields sub-Nodes.
+
+ Dotfiles are skipped.
+ """
+
+ return self.childnodes()
+
+ @property
+ def root_path (self) :
+ """
+ Build and return a relative path to the root of this dir tree
+ """
+
+ # build using parent root_path
+ return os.path.join('..', self.parent.root_path)
+
+ def children (self) :
+ """
+ Yield a series of Node subclasses representing the items in this dir.
+
+ This uses self.NODE_TYPES to figure out what kind of sub-node object to build. This should be a list of
+ (test_func, node_type)
+
+ tuples, of which the first is a function that takes a Node as it's sole argument, and returns a boolean.
+ For the first test_func which returns True, a Node-subclass object is constructed using node_type.from_node.
+ """
+
+ for node in self :
+ # figure out what type to use
+ for test_func, node_type in self.NODE_TYPES :
+ if test_func(node) :
+ # matches, build
+ yield node_type.from_node(node)
+
+ else :
+ # unknown file type!
+ raise Exception("unrecongized type of file: %s" % node);
+
+# assign default Directory.NODE_TYPES
+Directory.NODE_TYPES = [
+ (Node.is_dir, Directory),
+ (Node.is_file, File),
+]
+
+
+class Root (Directory) :
+ """
+ A special Directory that overrides the Node methods to anchor the recursion/etc at some 'real' filesystem path.
+ """
+
+ def __init__ (self, fspath, config) :
+ """
+ Construct the directory tree root at the given 'real' path, which must be a raw str
+ """
+
+ # abuse Node's concept of a "name" a bit
+ super(Root, self).__init__(None, fspath, config=config)
+
+ @property
+ def path (self) :
+ """
+ Returns the raw path
+ """
+
+ return self.fsname
+
+ @property
+ def unicodepath (self) :
+ """
+ Returns the raw decoded path
+ """
+
+ return self.name
+
+ @property
+ def root_path (self) :
+ """
+ Returns an empty string representing this dir
+ """
+
+ return ''
+
+ def __repr__ (self) :
+ """
+ Override Node.__repr__ to not use self.parent.path
+ """
+
+ return "Root(%r)" % self.fsname
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/degal/render.py Thu Jun 04 11:22:52 2009 +0300
@@ -0,0 +1,51 @@
+"""
+ Rendering images as thumbnails/previews.
+"""
+
+import PIL.Image
+
+import os.path
+
+class RenderMachine (object) :
+ """
+ RAWR! I'm the render machine!
+
+ TODO: use multithreaded rendering (PIL supports it)
+ """
+
+ def __init__ (self, config) :
+ """
+ Use the given Configuration object's settings for rendering
+ """
+
+ self.config = config
+
+ def render_out (self, img, size, out_path) :
+ """
+ Creates a thumbnail from the given PIL.Image of the given size, and saves it at out_path.
+ """
+
+ # we need to create a copy, as .thumbnail mutates the Image
+ img_out = img.copy()
+
+ # then resample to given size
+ img_out.thumbnail(size, resample=True)
+
+ # and write out
+ img_out.save(out_path)
+
+ def render_img (self, img, dirname, filename) :
+ """
+ Renders new thumbnails/previews from the given loaded image and filename based on the current configuration.
+
+ Note: this does not check for the existance of the thumbs/previews subdirs!
+ """
+
+ # once for each size
+ for subdir, size in (
+ (self.config.thumb_dir, self.config.thumb_size),
+ (self.config.preview_dir, self.config.preview_size)
+ ) :
+ # resize and write out
+ self.render_out(img, size, os.path.join(dirname, subdir, filename)
+