terom@57: """ terom@57: Per-image gallery state terom@57: """ terom@57: terom@57: from __future__ import with_statement terom@57: terom@57: import filesystem, render, html terom@12: terom@12: import PIL.Image terom@57: from lib import EXIF terom@33: terom@57: class Image (filesystem.File) : terom@57: """ terom@57: An Image is a filesystem File that represents an image that can be thumbnailed, and handled. terom@57: """ terom@44: terom@57: def __init__ (self, node, prev) : terom@57: """ terom@57: Initialize as an Image based on the given Node, linked with the given previous node terom@57: """ terom@57: terom@57: super(Image, self).__init__(node) terom@57: terom@57: # links terom@57: self.prev = prev terom@57: self.next = None terom@57: terom@57: # the .html file for this image terom@57: self.html = self.parent.subfile(self.basename + '.html') terom@57: terom@57: # info terom@57: self.title = self.name terom@57: self.description = None terom@57: terom@57: # lazy-loading terom@57: self.img = None terom@57: self.exif = None terom@57: self.metadata = None terom@57: self.stat_data = None terom@57: terom@57: def load_stat (self) : terom@57: """ terom@57: Load and return the os.stat info for this file terom@57: """ terom@57: terom@57: if not self.stat_data : terom@57: self.stat_data = self.stat() terom@57: terom@57: return self.stat_data terom@57: terom@57: def load_image (self) : terom@57: """ terom@57: Loads the image as a PIL.Image terom@57: """ terom@57: terom@57: if not self.img : terom@57: # open it up terom@57: self.img = PIL.Image.open(self.path) terom@57: terom@57: return self.img terom@57: terom@57: def load_exif (self) : terom@57: """ terom@57: Loads the EXIF data for the image and returns as a dict of EXIF tag names -> values. terom@57: terom@57: Returns None if no exif data was found terom@57: """ terom@57: terom@57: if self.exif is None : terom@57: # load terom@57: with self.open('rb') as fh : terom@57: # XXX: details=False? terom@57: self.exif = EXIF.process_file(fh) terom@57: terom@57: # empty dict -> no exif data terom@57: if self.exif : terom@57: return self.exif terom@57: terom@57: else : terom@57: return None terom@57: terom@57: def load_metadata (self) : terom@57: """ terom@57: Load and return the metadata for the image as a dictionary terom@57: """ terom@57: terom@57: if not self.metadata : terom@57: # load stuff terom@57: stat = self.load_stat() terom@57: exif = self.load_exif() terom@57: terom@57: # XXX: avoid having to open the image? terom@57: size = self.load_image().size terom@57: terom@57: # build terom@57: self.metadata = dict({ terom@57: "File name": self.name, terom@57: "Resolution": "%dx%d" % size, terom@57: "File size": format.filesize(stat.st_size), terom@57: "Last modified": format.filetime(stat.st_mtime), terom@57: }, **dict( terom@57: (name, exif[tag]) for tag, name in self.config.exif_tags if exif and tag in exif terom@57: )) terom@57: terom@57: return self.metadata terom@57: terom@57: def render_image (self) : terom@57: """ terom@57: Renders new thumbnails/previews for this image. terom@57: terom@57: Note: this does not check for the existance of the thumbs/previews subdirs! terom@57: """ terom@57: terom@57: # load origional image terom@57: img = self.load_image() terom@57: terom@57: # renderer to use terom@57: # XXX: get from elsewhere terom@57: render_machine = self.config.get_renderer() terom@57: terom@57: # lazy-render both thumb and preview terom@57: self.thumb = render_machine.render_lazy(self terom@57: self.config.thumb_size, self.parent.load_thumb_dir.subnode(self.name) terom@57: ) terom@57: terom@57: self.preview = render_machine.render_lazy(self, terom@57: self.config.preview_size, self.parent.load_preview_dir.subnode(self.name) terom@57: ) terom@44: terom@12: class Image (object) : terom@12: def __init__ (self, dir, name) : terom@12: # the image filename, e.g. DSC3948.JPG terom@27: self.name = unicode(name) terom@12: terom@12: # the Folder object that we are in terom@12: self.dir = dir terom@12: terom@12: # the relative path from the root to us terom@27: self.path = dir.pathFor(self.name) terom@12: terom@12: # the basename+ext, e.g. DSCR3948, .JPG terom@27: self.base_name, self.ext = os.path.splitext(self.name) terom@12: terom@12: # our user-friendly title terom@27: self.title = self.name terom@12: terom@12: # our long-winded description terom@12: self.descr = '' terom@12: terom@12: # the image before and after us, both may be None terom@12: self.prev = self.next = None terom@12: terom@12: # the image-relative names for the html page, thumb and preview images terom@12: self.html_name = self.name + ".html" terom@12: self.thumb_name = utils.url_join(settings.THUMB_DIR, self.name) terom@12: self.preview_name = utils.url_join(settings.PREVIEW_DIR, self.name) terom@12: terom@12: # the root-relative paths to the html page, thumb and preview images terom@12: self.html_path = self.dir.pathFor(self.html_name) terom@12: self.thumb_path = self.dir.pathFor(settings.THUMB_DIR, self.name) terom@12: self.preview_path = self.dir.pathFor(settings.PREVIEW_DIR, self.name) terom@12: terom@12: # terom@12: # Figured out after prepare terom@12: # terom@12: terom@12: # (w, h) tuple terom@12: self.img_size = None terom@12: terom@12: # the ShortURL code for this image terom@12: self.shorturl_code = None terom@12: terom@57: # EXIF data terom@57: self.exif_data = {} terom@33: terom@12: # what to use in the rendered templates, intended to be overridden by subclasses terom@12: self.series_act = "add" terom@12: self.series_verb = "Add to" terom@12: terom@12: def getObjInfo (self) : terom@12: """ terom@12: Metadata for shorturl2.db terom@12: """ terom@12: return 'img', self.dir.path, self.name terom@12: terom@12: def breadcrumb (self) : terom@12: """ terom@12: Returns a [(fname, title)] list of this image's parents terom@12: """ terom@12: terom@13: return self.dir.breadcrumb(forImg=self) + [(self.html_name, self.title)] terom@12: terom@12: def render (self) : terom@12: """ terom@12: Write out the .html file terom@12: """ terom@12: terom@12: # stat the image file to get the filesize and mtime terom@12: st = os.stat(self.path) terom@12: terom@12: self.filesize = st.st_size terom@12: self.timestamp = st.st_mtime terom@12: terom@12: # open the image in PIL to get image attributes + generate thumbnails terom@12: img = PIL.Image.open(self.path) terom@12: terom@12: self.img_size = img.size terom@12: terom@12: for out_path, geom in ((self.thumb_path, settings.THUMB_GEOM), (self.preview_path, settings.PREVIEW_GEOM)) : terom@12: # if it doesn't exist, or it's older than the image itself, generate terom@28: if utils.mtime(out_path) < self.timestamp : terom@28: log.info("render [%sx%s]", geom[0], geom[1], wait=True) terom@12: terom@12: # XXX: is this the most efficient way to do this? It seems slow terom@12: out_img = img.copy() terom@12: out_img.thumbnail(geom, resample=True) terom@12: out_img.save(out_path) terom@28: terom@28: log.done() terom@12: terom@12: # look for the metadata file terom@12: title_path = self.dir.pathFor(self.base_name + '.txt') terom@12: terom@12: self.title, self.descr = utils.readTitleDescr(title_path) terom@12: terom@12: if not self.title : terom@12: self.title = self.name terom@28: terom@28: if utils.mtime(self.html_path) < self.timestamp : terom@28: log.info("render %s.html", self.name) terom@12: terom@33: # parse the exif data from the file terom@33: try : terom@33: self.exif_data = dexif.parse_exif(self.path) terom@33: except dexif.ExifError, message: terom@33: log.warning("Reading EXIF data for %s failed: %s" % (self.filename, message)) terom@33: self.exif_data = {} terom@33: terom@33: terom@28: image_tpl.render_to(self.html_path, terom@57: stylesheet_url = self.dir.inRoot('style.css'), terom@57: title = self.title, terom@57: breadcrumb = self.breadcrumb(), terom@28: terom@57: prev = self.prev, terom@57: next = self.next, terom@57: img = self, terom@28: terom@57: description = self.descr, terom@28: terom@57: filename = self.name, terom@57: img_size = self.img_size, terom@57: file_size = self.filesize, terom@57: timestamp = self.timestamp, terom@57: exif_data = self.exif_data, terom@57: terom@57: shorturl = self.dir.inRoot('s', self.shorturl_code), terom@57: shorturl_code = self.shorturl_code, terom@57: terom@57: series_url = self.dir.inRoot('series/%s/%s' % (self.series_act, self.shorturl_code)), terom@57: series_verb = self.series_verb, terom@28: ) terom@12: terom@12: def __str__ (self) : terom@12: return "Image `%s' in `%s'" % (self.name, self.dir.path) terom@28: