start implementing new Image stuff, tie in RenderMachine into the new Image class, assoicated config stuff
authorTero Marttila <terom@fixme.fi>
Fri, 05 Jun 2009 19:30:15 +0300
changeset 57 8d06e0283b88
parent 56 80658a2eebf6
child 58 188a469792c2
start implementing new Image stuff, tie in RenderMachine into the new Image class, assoicated config stuff
degal/config.py
degal/image.py
degal/render.py
--- 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