changeset 265:37b96db69d0e

MoinImage macro: renamed to Image
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sat, 13 Sep 2008 23:56:05 +0200
parents b7e7be4340b0
children 3e4eaae2f7df
files data/plugin/macro/Image.py data/plugin/macro/MoinImage.py
diffstat 2 files changed, 235 insertions(+), 240 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/plugin/macro/Image.py	Sat Sep 13 23:56:05 2008 +0200
@@ -0,0 +1,235 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MoinImage image rendering support class and a test 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
+                 description=u'', # we just store this, but we do not use it in Image
+        ):
+        self.request = request
+        self.pagename, self.attachname = item_name.rsplit('/', 1)
+        self.description = description
+
+        # cached property values:
+        self.__filename = None
+        self.__content_type = None
+        self.__cache_key = None
+        self.__image = None
+        self.__exif = None
+        self.__ctime = None
+
+    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_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 widht 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")>>
+    """
+    img = Image(macro.request, itemname, alt)
+    return macro.formatter.image(src=img.url((width, height)), alt=img.description)
+
--- a/data/plugin/macro/MoinImage.py	Sat Sep 13 22:26:31 2008 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - MoinImage image rendering support class and a test 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:
-    import Image
-except ImportError:
-    Image = None
-try:
-    import IptcImagePlugin
-except ImportError:
-    IptcImagePlugin = None
-
-try:
-    import ExifTags
-except ImportError:
-    ExifTags = None
-
-class MoinImage(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
-                 description=u'', # we just store this, but we do not use it in MoinImage
-        ):
-        self.request = request
-        self.pagename, self.attachname = item_name.rsplit('/', 1)
-        self.description = description
-
-        # cached property values:
-        self.__filename = None
-        self.__content_type = None
-        self.__cache_key = None
-        self.__image = None
-        self.__exif = None
-        self.__ctime = None
-
-    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 Image is not None:
-            self.__image = Image.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 IptcImagePlugin: # we have PIL and the IPTC plugin
-                        iptc = IptcImagePlugin.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_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, Image.ANTIALIAS)
-
-            transpose_func = {
-                1: lambda image: image,
-                2: lambda image: image.transpose(Image.FLIP_LEFT_RIGHT),
-                3: lambda image: image.transpose(Image.ROTATE_180),
-                4: lambda image: image.transpose(Image.FLIP_TOP_BOTTOM),
-                5: lambda image: image.transpose(Image.ROTATE_90).transpose(Image.FLIP_TOP_BOTTOM),
-                6: lambda image: image.transpose(Image.ROTATE_270),
-                7: lambda image: image.transpose(Image.ROTATE_90).transpose(Image.FLIP_LEFT_RIGHT),
-                8: lambda image: image.transpose(Image.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_MoinImage(macro, itemname=wikiutil.required_arg(unicode), desc=u''):
-    """ Embed an Image into a wiki page -
-        currently more for testing MoinImage class than for practical use:
-
-        <<MoinImage(PageName/attachname,sample image)>>
-    """
-    request = macro.request
-    fmt = macro.formatter
-    thispagename = fmt.page.page_name
-    pagename, fname = AttachFile.absoluteName(itemname, thispagename)
-    img = MoinImage(request, itemname, desc)
-    return (fmt.image(src=img.url(img.THUMBNAIL_SIZE), alt=img.description) +
-            fmt.text(time.asctime(time.gmtime(img.ctime))) +
-            ' ' +
-            fmt.text(img.description) +
-            '---' +
-            fmt.text(repr(img.iptc)))
-