"""
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)