degal/image.py
author Tero Marttila <terom@fixme.fi>
Fri, 05 Jun 2009 19:30:15 +0300
changeset 57 8d06e0283b88
parent 44 533b7e8b5d3b
child 70 67dd32adf159
permissions -rw-r--r--
start implementing new Image stuff, tie in RenderMachine into the new Image class, assoicated config stuff
"""
    Per-image gallery state
"""

from __future__ import with_statement

import filesystem, render, html

import PIL.Image
from lib import EXIF

class Image (filesystem.File) :
    """
        An Image is a filesystem File that represents an image that can be thumbnailed, and handled.
    """

    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) :
        # the image filename, e.g. DSC3948.JPG
        self.name = unicode(name)

        # the Folder object that we are in
        self.dir = dir
        
        # the relative path from the root to us
        self.path = dir.pathFor(self.name)

        # the basename+ext, e.g. DSCR3948, .JPG
        self.base_name, self.ext = os.path.splitext(self.name)
        
        # our user-friendly title
        self.title = self.name

        # our long-winded description
        self.descr = ''

        # the image before and after us, both may be None
        self.prev = self.next = None
        
        # the image-relative names for the html page, thumb and preview images
        self.html_name = self.name + ".html"
        self.thumb_name = utils.url_join(settings.THUMB_DIR, self.name)
        self.preview_name = utils.url_join(settings.PREVIEW_DIR, self.name)

        # the root-relative paths to the html page, thumb and preview images
        self.html_path = self.dir.pathFor(self.html_name)
        self.thumb_path = self.dir.pathFor(settings.THUMB_DIR, self.name)
        self.preview_path = self.dir.pathFor(settings.PREVIEW_DIR, self.name)        
        
        #
        # Figured out after prepare
        #

        # (w, h) tuple
        self.img_size = None
        
        # the ShortURL code for this image
        self.shorturl_code = None

        # EXIF data
        self.exif_data = {}

        # 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 breadcrumb (self) :
        """
            Returns a [(fname, title)] list of this image's parents
       """
        
        return self.dir.breadcrumb(forImg=self) + [(self.html_name, self.title)]

    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, settings.THUMB_GEOM), (self.preview_path, settings.PREVIEW_GEOM)) :
            # if it doesn't exist, or it's older than the image itself, generate
            if utils.mtime(out_path) < self.timestamp :
                log.info("render [%sx%s]", geom[0], geom[1], wait=True)
                
                # XXX: is this the most efficient way to do this? It seems slow
                out_img = img.copy()
                out_img.thumbnail(geom, resample=True)
                out_img.save(out_path)

                log.done()
        
        # look for the metadata file
        title_path = self.dir.pathFor(self.base_name + '.txt')
        
        self.title, self.descr = utils.readTitleDescr(title_path)
        
        if not self.title :
            self.title = self.name
        
        if utils.mtime(self.html_path) < self.timestamp :
            log.info("render %s.html", self.name)

            # parse the exif data from the file
            try :
                    self.exif_data = dexif.parse_exif(self.path)
            except dexif.ExifError, message:
                    log.warning("Reading EXIF data for %s failed: %s" % (self.filename, message))
                    self.exif_data = {}


            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,
                
                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) :
        return "Image `%s' in `%s'" % (self.name, self.dir.path)