start implementing new Image stuff, tie in RenderMachine into the new Image class, assoicated config stuff
--- a/degal/config.py Fri Jun 05 19:29:04 2009 +0300
+++ b/degal/config.py Fri Jun 05 19:30:15 2009 +0300
@@ -2,6 +2,8 @@
Configuration
"""
+import render
+
class Configuration (object) :
"""
Various configuration settings
@@ -21,3 +23,34 @@
thumb_size = (160, 120)
preview_size = (640, 480)
+ # number of images displayed per folder page
+ images_per_page = 50
+
+ # exif tags used in output
+ # Copyright (C) 2008, Santtu Pajukanta <santtu@pajukanta.fi>
+ # XXX: import from dexif?
+ exif_tags = [
+ # TODO Create date is in a useless format, needs some strptime love
+ ("CreateDate", "Create date" ),
+ ("Model", "Camera model" ),
+ ("Aperture", "Aperture" ),
+ ("ExposureMode", "Exposure mode" ),
+ ("ExposureCompensation", "Exposure compensation" ),
+ ("ExposureTime", "Exposure time" ),
+ ("Flash", "Flash mode" ),
+ ("ISO", "ISO" ),
+ ("ShootingMode", "Shooting mode" ),
+ ("LensType", "Lens type" ),
+ ("FocalLength", "Focal length" )
+ ]
+
+ def is_image (self, file) :
+ """
+ Tests if the given File is an image, based on its file extension
+ """
+
+ return file.matchext(self.image_exts)
+
+ # XXX: move elsewhere?
+ def get_renderer (self) :
+ return render.RenderMachine(self)
--- a/degal/image.py Fri Jun 05 19:29:04 2009 +0300
+++ b/degal/image.py Fri Jun 05 19:30:15 2009 +0300
@@ -1,15 +1,131 @@
-import os, os.path
+"""
+ Per-image gallery state
+"""
+
+from __future__ import with_statement
+
+import filesystem, render, html
import PIL.Image
-
-import dexif
+from lib import EXIF
-import settings, utils, log
-from template import image as image_tpl
+class Image (filesystem.File) :
+ """
+ An Image is a filesystem File that represents an image that can be thumbnailed, and handled.
+ """
-"""
- Handling induvidual Images
-"""
+ def __init__ (self, node, prev) :
+ """
+ Initialize as an Image based on the given Node, linked with the given previous node
+ """
+
+ super(Image, self).__init__(node)
+
+ # links
+ self.prev = prev
+ self.next = None
+
+ # the .html file for this image
+ self.html = self.parent.subfile(self.basename + '.html')
+
+ # info
+ self.title = self.name
+ self.description = None
+
+ # lazy-loading
+ self.img = None
+ self.exif = None
+ self.metadata = None
+ self.stat_data = None
+
+ def load_stat (self) :
+ """
+ Load and return the os.stat info for this file
+ """
+
+ if not self.stat_data :
+ self.stat_data = self.stat()
+
+ return self.stat_data
+
+ def load_image (self) :
+ """
+ Loads the image as a PIL.Image
+ """
+
+ if not self.img :
+ # open it up
+ self.img = PIL.Image.open(self.path)
+
+ return self.img
+
+ def load_exif (self) :
+ """
+ Loads the EXIF data for the image and returns as a dict of EXIF tag names -> values.
+
+ Returns None if no exif data was found
+ """
+
+ if self.exif is None :
+ # load
+ with self.open('rb') as fh :
+ # XXX: details=False?
+ self.exif = EXIF.process_file(fh)
+
+ # empty dict -> no exif data
+ if self.exif :
+ return self.exif
+
+ else :
+ return None
+
+ def load_metadata (self) :
+ """
+ Load and return the metadata for the image as a dictionary
+ """
+
+ if not self.metadata :
+ # load stuff
+ stat = self.load_stat()
+ exif = self.load_exif()
+
+ # XXX: avoid having to open the image?
+ size = self.load_image().size
+
+ # build
+ self.metadata = dict({
+ "File name": self.name,
+ "Resolution": "%dx%d" % size,
+ "File size": format.filesize(stat.st_size),
+ "Last modified": format.filetime(stat.st_mtime),
+ }, **dict(
+ (name, exif[tag]) for tag, name in self.config.exif_tags if exif and tag in exif
+ ))
+
+ return self.metadata
+
+ def render_image (self) :
+ """
+ Renders new thumbnails/previews for this image.
+
+ Note: this does not check for the existance of the thumbs/previews subdirs!
+ """
+
+ # load origional image
+ img = self.load_image()
+
+ # renderer to use
+ # XXX: get from elsewhere
+ render_machine = self.config.get_renderer()
+
+ # lazy-render both thumb and preview
+ self.thumb = render_machine.render_lazy(self
+ self.config.thumb_size, self.parent.load_thumb_dir.subnode(self.name)
+ )
+
+ self.preview = render_machine.render_lazy(self,
+ self.config.preview_size, self.parent.load_preview_dir.subnode(self.name)
+ )
class Image (object) :
def __init__ (self, dir, name) :
@@ -54,8 +170,8 @@
# the ShortURL code for this image
self.shorturl_code = None
- # EXIF data
- self.exif_data = {}
+ # EXIF data
+ self.exif_data = {}
# what to use in the rendered templates, intended to be overridden by subclasses
self.series_act = "add"
@@ -122,27 +238,27 @@
image_tpl.render_to(self.html_path,
- stylesheet_url = self.dir.inRoot('style.css'),
- title = self.title,
- breadcrumb = self.breadcrumb(),
-
- prev = self.prev,
- next = self.next,
- img = self,
-
- description = self.descr,
+ stylesheet_url = self.dir.inRoot('style.css'),
+ title = self.title,
+ breadcrumb = self.breadcrumb(),
- filename = self.name,
- img_size = self.img_size,
- file_size = self.filesize,
- timestamp = self.timestamp,
- exif_data = self.exif_data,
+ prev = self.prev,
+ next = self.next,
+ img = self,
- shorturl = self.dir.inRoot('s', self.shorturl_code),
- shorturl_code = self.shorturl_code,
+ description = self.descr,
- series_url = self.dir.inRoot('series/%s/%s' % (self.series_act, self.shorturl_code)),
- series_verb = self.series_verb,
+ filename = self.name,
+ img_size = self.img_size,
+ file_size = self.filesize,
+ timestamp = self.timestamp,
+ exif_data = self.exif_data,
+
+ 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) :
--- a/degal/render.py Fri Jun 05 19:29:04 2009 +0300
+++ b/degal/render.py Fri Jun 05 19:30:15 2009 +0300
@@ -4,8 +4,6 @@
import PIL.Image
-import os.path
-
class RenderMachine (object) :
"""
RAWR! I'm the render machine!
@@ -20,11 +18,16 @@
self.config = config
- def render_out (self, img, size, out_path) :
+ def render_out (self, image, size, file) :
"""
- Creates a thumbnail from the given PIL.Image of the given size, and saves it at out_path.
+ Creates a thumbnail from the given Image of the given `size`, and saves it at `out`.
+
+ Returns the file for convenience
"""
+ # load the PIL.Image
+ img = image.load_image()
+
# we need to create a copy, as .thumbnail mutates the Image
img_out = img.copy()
@@ -32,20 +35,23 @@
img_out.thumbnail(size, resample=True)
# and write out
- img_out.save(out_path)
+ img_out.save(file.path)
- def render_img (self, img, dirname, filename) :
+ return file
+
+ def render_lazy (self, image, size, out) :
"""
- 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!
+ Renders the given image with render_out if `out` does not yet exist or is older than image.
"""
- # 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)
+ # stat the output file
+ out_stat = out.stat(soft=True)
+ # compare
+ if not out_stat or out_stat.st_mtime < image.load_stat().st_mtime :
+ # render anew
+ return self.render_out(image, size, file)
+
+ else :
+ # already rendered
+ return file