Mercurial > moin > extensions
view data/plugin/macro/Image.py @ 275:2a92ddf8a7c8
Image._get_description: strip whitespae from image name
author | Reimar Bauer <rb.proj AT googlemail DOT com> |
---|---|
date | Tue, 16 Sep 2008 11:48:52 +0200 |
parents | 1e3e876e38fb |
children | 03071c3569d8 |
line wrap: on
line source
# -*- coding: iso-8859-1 -*- """ MoinMoin - Image rendering support class and a simple macro using it. Features: * rendering (and caching) of thumbnails/webnails/originals * gives ready-to-use image urls * auto-rotation based on EXIF information * determines creation time from EXIF or file system * easy access to some EXIF data (cached) Requires PIL and ExifTags libs. @copyright: 2008 MoinMoin:ThomasWaldmann, 2008 MoinMoin:ReimarBauer @license: GNU GPL, see COPYING for details. """ import os, time import StringIO from MoinMoin import caching, wikiutil from MoinMoin.action import AttachFile, cache try: from PIL import Image as PILImage except ImportError: PILImage = None try: from PIL import IptcImagePlugin as PILIptc except ImportError: PILIptc = None try: import ExifTags except ImportError: ExifTags = None class Image(object): # predefined sizes (width, height) - use them if you like: WEBNAIL_SIZE = (640, 480) THUMBNAIL_SIZE = (128, 128) # we don't process and cache all EXIF infos, but just these: EXIF_CACHED = set(['DateTimeOriginal', 'TimeZoneOffset', 'Orientation', ]) def __init__(self, request, item_name, # PageName/AttachName for now, later this is just the item name headline=None, caption=None, raw=None, ): self.request = request self.pagename, self.attachname = item_name.rsplit('/', 1) self.raw = raw # cached property values: self.__filename = None self.__content_type = None self.__cache_key = None self.__image = None self.__exif = None self.__ctime = None self.__headline = headline self.__caption = caption self.__description = self.attachname def _get_description(self): quotes = self.raw.split('\n') quotes = [quote.strip() for quote in quotes] quotes = [quote[2:] for quote in quotes if quote.startswith('* ')] image_alias = self.attachname for line in quotes: if line.startswith('[[') and line.endswith(']]'): img, alias = line[2:-2].split('|', 1) if img.strip() == self.attachname: self.__description = alias.strip() break return self.__description description = property(_get_description) def _get_filename(self): if self.__filename is None: self.__filename = AttachFile.getFilename(self.request, self.pagename, self.attachname) return self.__filename _filename = property(_get_filename) def _get_content_type(self): if self.__content_type is None: self.__content_type = wikiutil.MimeType(filename=self._filename).mime_type() return self.__content_type content_type = property(_get_content_type) def _get_image(self): if self.__image is None and PILImage is not None: self.__image = PILImage.open(self._filename) return self.__image image = property(_get_image) # the original image (PIL Image object) or None def _get_cache_key(self): if self.__cache_key is None: self.__cache_key = cache.key(self.request, itemname=self.pagename, attachname=self.attachname) return self.__cache_key _cache_key = property(_get_cache_key) def _get_exif_data(self): """ return exif data for this image, use a cache for them """ if self.__exif is None: key = self._cache_key exif_cache = caching.CacheEntry(self.request, 'exif', key, 'wiki', use_pickle=True, do_locking=False) if not exif_cache.exists(): # we don't want to cache all EXIF data, just a few interesting values try: exif_data = {} if self.image is not None: # we have PIL for tag, value in self.image._getexif().items(): tag_name = ExifTags.TAGS.get(tag) if tag_name in self.EXIF_CACHED: exif_data[tag_name] = value try: time_str = exif_data['DateTimeOriginal'] tm = time.strptime(time_str, "%Y:%m:%d %H:%M:%S") t = time.mktime(tm) try: tz_str = exif_data['TimeZoneOffset'] tz_offset = int(tz_str) * 3600 except: tz_offset = 0 # mktime is inverse function of (our) localtime, adjust by time.timezone t -= time.timezone + tz_offset except: t = 0 exif_data['DateTimeOriginal'] = t except (IOError, AttributeError): exif_data = {} try: iptc_data = {} if self.image is not None and PILIptc: # we have PIL and the IPTC plugin iptc = PILIptc.getiptcinfo(self.image) or {} for name, key in [ ('headline', (2, 105)), ('caption', (2, 120)), ('copyright', (2, 116)), ('keywords', (2, 25)), ]: try: iptc_data[name] = iptc[key] except KeyError: pass except: iptc_data = {} cache_data = (exif_data, iptc_data) exif_cache.update(cache_data) self.__exif, self.__iptc = cache_data else: self.__exif, self.__iptc = exif_cache.content() return self.__exif, self.__iptc exif = property(lambda self: self._get_exif_data()[0]) # dict of preprocessed EXIF data (string -> value) iptc = property(lambda self: self._get_exif_data()[1]) # dict of preprocessed IPTC data (string -> value) def _get_headline(self): if self.__headline is None: self.__headline = self.iptc.get('headline', u'') return self.__headline headline = property(_get_headline) def _get_caption(self): if self.__caption is None: self.__caption = self.iptc.get('caption', u'') return self.__caption caption = property(_get_caption) def _get_ctime(self): """ return creation time of image (either from EXIF or file date) as UNIX timestamp """ if self.__ctime is None: try: ctime = self.exif['DateTimeOriginal'] except KeyError: ctime = os.path.getctime(self._filename) self.__ctime = ctime return self.__ctime ctime = property(_get_ctime) def _transform(self, size=None, content_type=None, transpose_op=None): """ resize to new size (optional), transpose according to exif infos, return data as content_type (default: same ct as original image) """ if content_type is None: content_type = self.content_type if content_type == 'image/jpeg': output_type = 'JPEG' elif content_type == 'image/png': output_type = 'PNG' elif content_type == 'image/gif': output_type = 'GIF' else: raise ValueError("content_type %r not supported" % content_type) image = self.image if image is not None: # we have PIL try: # if we have EXIF data, we can transpose (e.g. rotate left), # so the rendered image is correctly oriented: transpose_op = transpose_op or self.exif['Orientation'] except KeyError: transpose_op = 1 # no change if size is not None: image = image.copy() # create copy first as thumbnail works in-place image.thumbnail(size, PILImage.ANTIALIAS) transpose_func = { 1: lambda image: image, 2: lambda image: image.transpose(PILImage.FLIP_LEFT_RIGHT), 3: lambda image: image.transpose(PILImage.ROTATE_180), 4: lambda image: image.transpose(PILImage.FLIP_TOP_BOTTOM), 5: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_TOP_BOTTOM), 6: lambda image: image.transpose(PILImage.ROTATE_270), 7: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_LEFT_RIGHT), 8: lambda image: image.transpose(PILImage.ROTATE_90), } image = transpose_func[transpose_op](image) buf = StringIO.StringIO() image.save(buf, output_type) buf.flush() # XXX needed? data = buf.getvalue() buf.close() else: # XXX what to do without PIL? data = '' return content_type, data def url(self, size=None): """ return a cache url for a rendering of this image with specified size - the code automatically makes sure that the cache contains that rendering. If size is None, it gives a url for the full image size rendering. Otherwise, size has to be a tuple (w, h) - if you like, you can use these class level constant sizes: WEBNAIL_SIZE - medium size, one of those likely to fit in a browser window THUMBNAIL_SIZE - small size, for showing collections """ request = self.request content_type = self.content_type if size is None: size_str = 'orig' else: size_str = '%d_%d' % size key = '%s_%s_%s' % (content_type.replace('/', '_'), size_str, self._cache_key) if not cache.exists(request, key): content_type, data = self._transform(size=size, content_type=content_type) cache.put(request, key, data, content_type=content_type) return cache.url(request, key) def macro_Image(macro, itemname=wikiutil.required_arg(unicode), width=9999, height=9999, alt=u''): """ Embed an Image into a wiki page. We use a very high default value for width and height, because PIL will calculate the image dimensions to not be larger than (width, height) and also not be larger than the original image. Thus, by not giving width or height, you'll get the original image, and if you specify width or height you will get an image of that width or that height. <<Image(PageName/attachname,width=100,alt="sample image")>> """ if '/' not in itemname: itemname = macro.formatter.page.page_name + '/' + itemname img = Image(macro.request, itemname, caption=alt) return macro.formatter.image(src=img.url((width, height)), alt=img.caption)